metergraph-core 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {metergraph_core-0.2.0/src/metergraph_core.egg-info → metergraph_core-0.2.2}/PKG-INFO +1 -1
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/pyproject.toml +1 -1
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core/__init__.py +10 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core/catalog.py +29 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core/data/prices.yaml +38 -2
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core/loader.py +102 -0
- metergraph_core-0.2.2/src/metergraph_core/retrieval.py +141 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2/src/metergraph_core.egg-info}/PKG-INFO +1 -1
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core.egg-info/SOURCES.txt +1 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/MANIFEST.in +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/README.md +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/setup.cfg +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core/billing.py +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core.egg-info/requires.txt +0 -0
- {metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core.egg-info/top_level.txt +0 -0
|
@@ -19,6 +19,12 @@ from .loader import (
|
|
|
19
19
|
LoadedCatalog,
|
|
20
20
|
load_catalog,
|
|
21
21
|
parse_catalog,
|
|
22
|
+
parse_retrieval,
|
|
23
|
+
)
|
|
24
|
+
from .retrieval import (
|
|
25
|
+
RetrievalCatalog,
|
|
26
|
+
RetrievalCostResult,
|
|
27
|
+
RetrievalPrice,
|
|
22
28
|
)
|
|
23
29
|
|
|
24
30
|
__all__ = [
|
|
@@ -31,9 +37,13 @@ __all__ = [
|
|
|
31
37
|
"GatewayBillingEvidence",
|
|
32
38
|
"Price",
|
|
33
39
|
"ResolvedPrice",
|
|
40
|
+
"RetrievalCatalog",
|
|
41
|
+
"RetrievalCostResult",
|
|
42
|
+
"RetrievalPrice",
|
|
34
43
|
"direct_channel_for_provider",
|
|
35
44
|
"load_catalog",
|
|
36
45
|
"normalize_gateway_evidence",
|
|
37
46
|
"parse_catalog",
|
|
47
|
+
"parse_retrieval",
|
|
38
48
|
"resolve_billing",
|
|
39
49
|
]
|
|
@@ -69,6 +69,7 @@ class Alias:
|
|
|
69
69
|
canonical_id: str
|
|
70
70
|
pricing_channel: str
|
|
71
71
|
rules: Mapping[str, Any]
|
|
72
|
+
publisher: str | None = None
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
@dataclass(frozen=True, slots=True)
|
|
@@ -270,6 +271,34 @@ class CatalogSnapshot:
|
|
|
270
271
|
return price
|
|
271
272
|
return None
|
|
272
273
|
|
|
274
|
+
def infer_direct_channel(self, model: Any) -> str | None:
|
|
275
|
+
"""Return the sole direct billing channel known for ``model``.
|
|
276
|
+
|
|
277
|
+
This supports captures that preserve a model identity but omit the
|
|
278
|
+
source provider. It uses the catalog's canonical model publisher and
|
|
279
|
+
returns ``None`` whenever model identity or direct channel is
|
|
280
|
+
ambiguous.
|
|
281
|
+
"""
|
|
282
|
+
if not isinstance(model, str) or not model.strip():
|
|
283
|
+
return None
|
|
284
|
+
model_key = model.strip().lower()
|
|
285
|
+
canonical_ids = {
|
|
286
|
+
alias.canonical_id
|
|
287
|
+
for (known_model, _channel), alias in self._deployment_aliases.items()
|
|
288
|
+
if known_model == model_key
|
|
289
|
+
}
|
|
290
|
+
if len(canonical_ids) != 1:
|
|
291
|
+
return None
|
|
292
|
+
channels: set[str] = set()
|
|
293
|
+
for canonical_id in canonical_ids:
|
|
294
|
+
for alias in self._deployment_aliases.values():
|
|
295
|
+
if alias.canonical_id != canonical_id:
|
|
296
|
+
continue
|
|
297
|
+
channel = direct_channel_for_provider(alias.publisher)
|
|
298
|
+
if channel is not None and (canonical_id, channel) in self._prices:
|
|
299
|
+
channels.add(channel)
|
|
300
|
+
return next(iter(channels)) if len(channels) == 1 else None
|
|
301
|
+
|
|
273
302
|
def resolve_price(
|
|
274
303
|
self, *, model: Any, channel: Any, at: datetime
|
|
275
304
|
) -> ResolvedPrice | None:
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Community updates welcome: add an alias or price entry with its provider
|
|
8
8
|
# source_url and open a PR. CI validates structure and date overlaps.
|
|
9
|
-
version: "2026-08-
|
|
9
|
+
version: "2026-08-26"
|
|
10
10
|
currency: USD
|
|
11
|
-
pricing_verified_at: "2026-08-
|
|
11
|
+
pricing_verified_at: "2026-08-26"
|
|
12
12
|
models:
|
|
13
13
|
- canonical_id: openai/gpt-5.6-sol
|
|
14
14
|
publisher: openai
|
|
@@ -975,3 +975,39 @@ models:
|
|
|
975
975
|
output_per_mtok: 0.50
|
|
976
976
|
cache_read_per_mtok: 0.05
|
|
977
977
|
source_url: https://vercel.com/ai-gateway/models/grok-4.1-fast-reasoning/providers
|
|
978
|
+
|
|
979
|
+
# Retrieval prices: USD per 1,000 counted operations (completed searches, tool
|
|
980
|
+
# calls, executed grounding queries), billed on the provider's direct channel.
|
|
981
|
+
# These sit alongside the model-token catalog above but are priced per unit, not
|
|
982
|
+
# per token. Effective-dated like model prices: keep superseded entries and close
|
|
983
|
+
# them with `effective_to` rather than editing in place. A $0 fee is an explicit
|
|
984
|
+
# priced zero, not the absence of a price.
|
|
985
|
+
retrieval:
|
|
986
|
+
- channel: anthropic-api
|
|
987
|
+
operation: web_search
|
|
988
|
+
region: global
|
|
989
|
+
unit: completed_search
|
|
990
|
+
per_1k_usd: 10.00
|
|
991
|
+
effective_from: "2026-08-26"
|
|
992
|
+
source_url: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/web-search-tool
|
|
993
|
+
- channel: anthropic-api
|
|
994
|
+
operation: web_fetch
|
|
995
|
+
region: global
|
|
996
|
+
unit: tool_call
|
|
997
|
+
per_1k_usd: 0.00
|
|
998
|
+
effective_from: "2026-08-26"
|
|
999
|
+
source_url: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/web-fetch-tool
|
|
1000
|
+
- channel: openai-api
|
|
1001
|
+
operation: web_search
|
|
1002
|
+
region: global
|
|
1003
|
+
unit: tool_call
|
|
1004
|
+
per_1k_usd: 10.00
|
|
1005
|
+
effective_from: "2026-08-26"
|
|
1006
|
+
source_url: https://platform.openai.com/docs/pricing
|
|
1007
|
+
- channel: google-api
|
|
1008
|
+
operation: google_search_grounding
|
|
1009
|
+
region: global
|
|
1010
|
+
unit: executed_query
|
|
1011
|
+
per_1k_usd: 14.00
|
|
1012
|
+
effective_from: "2026-08-26"
|
|
1013
|
+
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
@@ -17,6 +17,7 @@ from .catalog import (
|
|
|
17
17
|
_decimal,
|
|
18
18
|
_normalize_provider,
|
|
19
19
|
)
|
|
20
|
+
from .retrieval import RetrievalCatalog, RetrievalCostResult, RetrievalPrice
|
|
20
21
|
|
|
21
22
|
DEFAULT_CATALOG_PATH = Path(__file__).parent / "data" / "prices.yaml"
|
|
22
23
|
_PROVIDER_SYNONYMS = {"bedrock": ("aws-bedrock", "aws")}
|
|
@@ -72,6 +73,7 @@ class LoadedCatalog:
|
|
|
72
73
|
document: dict[str, Any]
|
|
73
74
|
snapshot: CatalogSnapshot
|
|
74
75
|
canonical_ids: Mapping[tuple[str, str], str]
|
|
76
|
+
retrieval: RetrievalCatalog
|
|
75
77
|
|
|
76
78
|
def canonical_model_id(self, provider: str, model_id: str) -> str:
|
|
77
79
|
"""Resolve a captured provider-native model id to its canonical id,
|
|
@@ -84,6 +86,10 @@ class LoadedCatalog:
|
|
|
84
86
|
(_normalize_provider(provider), model_id), model_id
|
|
85
87
|
)
|
|
86
88
|
|
|
89
|
+
def infer_direct_channel(self, model: Any) -> str | None:
|
|
90
|
+
"""Infer a direct pricing channel only when the catalog is unambiguous."""
|
|
91
|
+
return self.snapshot.infer_direct_channel(model)
|
|
92
|
+
|
|
87
93
|
def price(
|
|
88
94
|
self,
|
|
89
95
|
*,
|
|
@@ -109,6 +115,26 @@ class LoadedCatalog:
|
|
|
109
115
|
batch=batch,
|
|
110
116
|
)
|
|
111
117
|
|
|
118
|
+
def price_retrieval(
|
|
119
|
+
self,
|
|
120
|
+
*,
|
|
121
|
+
channel: Any,
|
|
122
|
+
operation: Any,
|
|
123
|
+
units: Any,
|
|
124
|
+
at: Any,
|
|
125
|
+
region: Any = "global",
|
|
126
|
+
) -> RetrievalCostResult:
|
|
127
|
+
"""Price a retrieval operation (searches, tool calls, grounded queries)
|
|
128
|
+
by counted unit. See :meth:`RetrievalCatalog.price`. This is kept
|
|
129
|
+
distinct from :meth:`price`, which costs model-token usage."""
|
|
130
|
+
return self.retrieval.price(
|
|
131
|
+
channel=channel,
|
|
132
|
+
operation=operation,
|
|
133
|
+
units=units,
|
|
134
|
+
at=at,
|
|
135
|
+
region=region,
|
|
136
|
+
)
|
|
137
|
+
|
|
112
138
|
|
|
113
139
|
def _date(value: Any, *, field: str, model: str) -> datetime:
|
|
114
140
|
if value is None:
|
|
@@ -150,6 +176,7 @@ def parse_catalog(
|
|
|
150
176
|
canonical = str(entry.get("canonical_id") or "")
|
|
151
177
|
if not canonical:
|
|
152
178
|
raise CatalogError("model entry missing canonical_id")
|
|
179
|
+
publisher = str(entry.get("publisher") or "").strip().lower() or None
|
|
153
180
|
for alias in entry.get("aliases") or []:
|
|
154
181
|
provider = str(alias.get("provider") or "").lower()
|
|
155
182
|
name = str(alias.get("alias") or "").lower()
|
|
@@ -167,6 +194,7 @@ def parse_catalog(
|
|
|
167
194
|
canonical_id=canonical,
|
|
168
195
|
pricing_channel=channel,
|
|
169
196
|
rules=_freeze(alias.get("rules") or {}),
|
|
197
|
+
publisher=publisher,
|
|
170
198
|
)
|
|
171
199
|
seen_windows: list[tuple[str, str, datetime, datetime | None]] = []
|
|
172
200
|
for price in entry.get("prices") or []:
|
|
@@ -222,6 +250,79 @@ def parse_catalog(
|
|
|
222
250
|
return version, aliases, prices
|
|
223
251
|
|
|
224
252
|
|
|
253
|
+
def parse_retrieval(document: Any) -> list[RetrievalPrice]:
|
|
254
|
+
"""Parse the optional top-level ``retrieval`` list into effective-dated
|
|
255
|
+
per-1,000-unit prices, applying the same effective-date validation as the
|
|
256
|
+
token catalog (ISO dates, ``effective_to`` after ``effective_from``, no
|
|
257
|
+
overlapping windows per channel/operation/region). A per-unit rate must be
|
|
258
|
+
present and non-negative. An absent ``retrieval`` key yields no prices."""
|
|
259
|
+
if not isinstance(document, dict):
|
|
260
|
+
raise CatalogError("prices document must be a mapping")
|
|
261
|
+
entries = document.get("retrieval")
|
|
262
|
+
if entries is None:
|
|
263
|
+
return []
|
|
264
|
+
if not isinstance(entries, list):
|
|
265
|
+
raise CatalogError("prices document retrieval must be a list")
|
|
266
|
+
prices: list[RetrievalPrice] = []
|
|
267
|
+
seen_windows: list[tuple[str, str, str, datetime, datetime | None]] = []
|
|
268
|
+
for entry in entries:
|
|
269
|
+
channel = str(entry.get("channel") or "").strip().lower()
|
|
270
|
+
operation = str(entry.get("operation") or "").strip().lower()
|
|
271
|
+
region = str(entry.get("region") or "global")
|
|
272
|
+
unit = str(entry.get("unit") or "").strip()
|
|
273
|
+
if not channel or not operation:
|
|
274
|
+
raise CatalogError("retrieval entry needs channel/operation")
|
|
275
|
+
label = f"{channel}/{operation}"
|
|
276
|
+
if not unit:
|
|
277
|
+
raise CatalogError(f"{label}: retrieval entry needs a unit")
|
|
278
|
+
if not str(entry.get("source_url") or "").strip():
|
|
279
|
+
raise CatalogError(f"{label}: retrieval entry needs source_url")
|
|
280
|
+
per_1k_usd = _decimal(entry.get("per_1k_usd"))
|
|
281
|
+
if per_1k_usd is None or per_1k_usd < 0:
|
|
282
|
+
raise CatalogError(
|
|
283
|
+
f"{label}: retrieval entry needs a non-negative per_1k_usd"
|
|
284
|
+
)
|
|
285
|
+
effective_from = _date(
|
|
286
|
+
entry.get("effective_from"), field="effective_from", model=label
|
|
287
|
+
)
|
|
288
|
+
effective_to = (
|
|
289
|
+
_date(entry.get("effective_to"), field="effective_to", model=label)
|
|
290
|
+
if entry.get("effective_to") is not None
|
|
291
|
+
else None
|
|
292
|
+
)
|
|
293
|
+
if effective_to is not None and effective_to <= effective_from:
|
|
294
|
+
raise CatalogError(f"{label}: effective_to before effective_from")
|
|
295
|
+
for other in seen_windows:
|
|
296
|
+
other_channel, other_operation, other_region, other_from, other_to = other
|
|
297
|
+
if (other_channel, other_operation, other_region) != (
|
|
298
|
+
channel,
|
|
299
|
+
operation,
|
|
300
|
+
region,
|
|
301
|
+
):
|
|
302
|
+
continue
|
|
303
|
+
if (effective_to is None or other_from < effective_to) and (
|
|
304
|
+
other_to is None or effective_from < other_to
|
|
305
|
+
):
|
|
306
|
+
raise CatalogError(
|
|
307
|
+
f"{label}: overlapping {region} retrieval price windows"
|
|
308
|
+
)
|
|
309
|
+
seen_windows.append((channel, operation, region, effective_from, effective_to))
|
|
310
|
+
prices.append(
|
|
311
|
+
RetrievalPrice(
|
|
312
|
+
id=f"{channel}:{operation}:{region}:{effective_from.date()}",
|
|
313
|
+
channel=channel,
|
|
314
|
+
operation=operation,
|
|
315
|
+
region=region,
|
|
316
|
+
unit=unit,
|
|
317
|
+
per_1k_usd=per_1k_usd,
|
|
318
|
+
effective_from=effective_from,
|
|
319
|
+
effective_to=effective_to,
|
|
320
|
+
source_url=str(entry["source_url"]).strip(),
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
return prices
|
|
324
|
+
|
|
325
|
+
|
|
225
326
|
def load_catalog(
|
|
226
327
|
path: str | Path | None = None, *, region: str = "global"
|
|
227
328
|
) -> LoadedCatalog:
|
|
@@ -238,4 +339,5 @@ def load_catalog(
|
|
|
238
339
|
document=document,
|
|
239
340
|
snapshot=CatalogSnapshot(aliases, prices, region=region),
|
|
240
341
|
canonical_ids=_canonical_index(document),
|
|
342
|
+
retrieval=RetrievalCatalog(parse_retrieval(document)),
|
|
241
343
|
)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Effective-dated retrieval pricing: searches, tool calls, grounded queries.
|
|
2
|
+
|
|
3
|
+
Retrieval pricing sits alongside -- and deliberately apart from -- the
|
|
4
|
+
model-token catalog. A retrieval operation is billed per counted unit (a
|
|
5
|
+
completed web search, a tool call, an executed grounding query), never per
|
|
6
|
+
token, so it carries its own price shape and its own catalog object.
|
|
7
|
+
|
|
8
|
+
The contract mirrors the token path's explicit priced/partial/unpriced
|
|
9
|
+
semantics: a resolved operation with a valid unit count is ``priced`` (a $0
|
|
10
|
+
fee is a real priced zero), while a missing, non-integer, or negative unit
|
|
11
|
+
count and an unknown operation or channel come back ``unpriced`` with a reason
|
|
12
|
+
-- never silently priced at zero.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from .catalog import _COST_QUANTUM, _coerce_datetime
|
|
21
|
+
|
|
22
|
+
_THOUSAND = Decimal("1000")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class RetrievalPrice:
|
|
27
|
+
"""An effective-dated per-1,000-unit price for one channel/operation."""
|
|
28
|
+
|
|
29
|
+
id: str
|
|
30
|
+
channel: str
|
|
31
|
+
operation: str
|
|
32
|
+
region: str
|
|
33
|
+
unit: str
|
|
34
|
+
per_1k_usd: Decimal
|
|
35
|
+
effective_from: datetime
|
|
36
|
+
effective_to: datetime | None
|
|
37
|
+
source_url: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class RetrievalCostResult:
|
|
42
|
+
"""The outcome of pricing a retrieval operation.
|
|
43
|
+
|
|
44
|
+
``status`` reuses the token path's vocabulary (``priced``/``partial``/
|
|
45
|
+
``unpriced``); retrieval never produces ``partial`` because a unit count is
|
|
46
|
+
all-or-nothing -- a bad count leaves nothing knowable to price.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
price_id: str | None
|
|
50
|
+
cost_usd: Decimal | None
|
|
51
|
+
status: str
|
|
52
|
+
reasons: tuple[str, ...] = ()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _units(value: Any) -> tuple[int | None, str | None]:
|
|
56
|
+
"""Coerce a retrieval unit count under a strict-integer contract.
|
|
57
|
+
|
|
58
|
+
Only a real, non-negative ``int`` is a valid count. ``bool`` (``type`` is
|
|
59
|
+
``bool``, not ``int``), ``float``, strings, and ``Decimal`` are rejected as
|
|
60
|
+
``invalid_units`` rather than truncated or parsed; ``None`` is
|
|
61
|
+
``missing_units`` and a negative int is ``negative_units``. Each rejection
|
|
62
|
+
keeps the operation explicitly unpriced instead of turning it into zero.
|
|
63
|
+
"""
|
|
64
|
+
if value is None:
|
|
65
|
+
return None, "missing_units"
|
|
66
|
+
if type(value) is not int:
|
|
67
|
+
return None, "invalid_units"
|
|
68
|
+
if value < 0:
|
|
69
|
+
return None, "negative_units"
|
|
70
|
+
return value, None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RetrievalCatalog:
|
|
74
|
+
"""Resolve and cost retrieval operations by ``(channel, operation)``.
|
|
75
|
+
|
|
76
|
+
Channel selection is exact and region resolution falls back
|
|
77
|
+
``region -> "*" -> "global"``, mirroring the token snapshot: an operation a
|
|
78
|
+
channel does not carry stays unpriced rather than being repriced off another
|
|
79
|
+
channel or region.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, prices: list[RetrievalPrice]) -> None:
|
|
83
|
+
self._by_key: dict[tuple[str, str], list[RetrievalPrice]] = {}
|
|
84
|
+
self._channels: set[str] = set()
|
|
85
|
+
for price in prices:
|
|
86
|
+
self._channels.add(price.channel)
|
|
87
|
+
self._by_key.setdefault((price.channel, price.operation), []).append(price)
|
|
88
|
+
for candidates in self._by_key.values():
|
|
89
|
+
candidates.sort(key=lambda price: price.effective_from, reverse=True)
|
|
90
|
+
|
|
91
|
+
def _select(
|
|
92
|
+
self, channel: str, operation: str, region: str, at: datetime
|
|
93
|
+
) -> RetrievalPrice | None:
|
|
94
|
+
at = at if at.tzinfo else at.replace(tzinfo=timezone.utc)
|
|
95
|
+
candidates = self._by_key.get((channel, operation), [])
|
|
96
|
+
for candidate_region in dict.fromkeys((region, "*", "global")):
|
|
97
|
+
for price in candidates:
|
|
98
|
+
if price.region.lower() != candidate_region:
|
|
99
|
+
continue
|
|
100
|
+
if price.effective_from <= at and (
|
|
101
|
+
price.effective_to is None or at < price.effective_to
|
|
102
|
+
):
|
|
103
|
+
return price
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def price(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
channel: Any,
|
|
110
|
+
operation: Any,
|
|
111
|
+
units: Any,
|
|
112
|
+
at: Any,
|
|
113
|
+
region: Any = "global",
|
|
114
|
+
) -> RetrievalCostResult:
|
|
115
|
+
"""Price ``units`` of ``operation`` on ``channel`` at time ``at``.
|
|
116
|
+
|
|
117
|
+
Resolution happens before unit validation: an unknown channel or
|
|
118
|
+
operation is reported as such regardless of the unit count, because an
|
|
119
|
+
operation that cannot be identified cannot be priced at all.
|
|
120
|
+
"""
|
|
121
|
+
channel_key = str(channel or "").strip().lower()
|
|
122
|
+
operation_key = str(operation or "").strip().lower()
|
|
123
|
+
region_key = str(region or "global").strip().lower() or "global"
|
|
124
|
+
|
|
125
|
+
if channel_key not in self._channels:
|
|
126
|
+
return RetrievalCostResult(None, None, "unpriced", ("unknown_channel",))
|
|
127
|
+
if (channel_key, operation_key) not in self._by_key:
|
|
128
|
+
return RetrievalCostResult(None, None, "unpriced", ("unknown_operation",))
|
|
129
|
+
|
|
130
|
+
price = self._select(channel_key, operation_key, region_key, _coerce_datetime(at))
|
|
131
|
+
if price is None:
|
|
132
|
+
return RetrievalCostResult(None, None, "unpriced", ("no_effective_price",))
|
|
133
|
+
|
|
134
|
+
count, reason = _units(units)
|
|
135
|
+
if reason is not None:
|
|
136
|
+
return RetrievalCostResult(None, None, "unpriced", (reason,))
|
|
137
|
+
|
|
138
|
+
cost = (Decimal(count) * price.per_1k_usd / _THOUSAND).quantize(
|
|
139
|
+
_COST_QUANTUM, rounding=ROUND_HALF_UP
|
|
140
|
+
)
|
|
141
|
+
return RetrievalCostResult(price.id, cost, "priced", ())
|
|
@@ -5,6 +5,7 @@ src/metergraph_core/__init__.py
|
|
|
5
5
|
src/metergraph_core/billing.py
|
|
6
6
|
src/metergraph_core/catalog.py
|
|
7
7
|
src/metergraph_core/loader.py
|
|
8
|
+
src/metergraph_core/retrieval.py
|
|
8
9
|
src/metergraph_core.egg-info/PKG-INFO
|
|
9
10
|
src/metergraph_core.egg-info/SOURCES.txt
|
|
10
11
|
src/metergraph_core.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{metergraph_core-0.2.0 → metergraph_core-0.2.2}/src/metergraph_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|