metergraph-core 0.2.54__tar.gz → 0.2.55__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.
Files changed (18) hide show
  1. {metergraph_core-0.2.54/src/metergraph_core.egg-info → metergraph_core-0.2.55}/PKG-INFO +1 -1
  2. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/pyproject.toml +1 -1
  3. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/__init__.py +8 -0
  4. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/retrieval.py +93 -0
  5. {metergraph_core-0.2.54 → metergraph_core-0.2.55/src/metergraph_core.egg-info}/PKG-INFO +1 -1
  6. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/MANIFEST.in +0 -0
  7. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/README.md +0 -0
  8. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/setup.cfg +0 -0
  9. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/billing.py +0 -0
  10. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/catalog.py +0 -0
  11. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/data/models.yaml +0 -0
  12. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/data/prices.yaml +0 -0
  13. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/loader.py +0 -0
  14. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core/models.py +0 -0
  15. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core.egg-info/SOURCES.txt +0 -0
  16. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
  17. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core.egg-info/requires.txt +0 -0
  18. {metergraph_core-0.2.54 → metergraph_core-0.2.55}/src/metergraph_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.2.54
3
+ Version: 0.2.55
4
4
  Summary: Reusable MeterGraph catalog and deterministic billing engine
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metergraph-core"
3
- version = "0.2.54"
3
+ version = "0.2.55"
4
4
  description = "Reusable MeterGraph catalog and deterministic billing engine"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -37,6 +37,10 @@ from .models import (
37
37
  validate_model_registry,
38
38
  )
39
39
  from .retrieval import (
40
+ count_fetch_units,
41
+ count_search_units,
42
+ fetch_operation_for_channel,
43
+ search_operation_for_channel,
40
44
  RetrievalCatalog,
41
45
  RetrievalCostResult,
42
46
  RetrievalPrice,
@@ -62,6 +66,8 @@ __all__ = [
62
66
  "RetrievalCatalog",
63
67
  "RetrievalCostResult",
64
68
  "RetrievalPrice",
69
+ "count_fetch_units",
70
+ "count_search_units",
65
71
  "counts_cache_read_in_input",
66
72
  "direct_channel_for_provider",
67
73
  "normalize_provider",
@@ -73,5 +79,7 @@ __all__ = [
73
79
  "parse_disputes",
74
80
  "parse_retrieval",
75
81
  "resolve_billing",
82
+ "fetch_operation_for_channel",
83
+ "search_operation_for_channel",
76
84
  "validate_model_registry",
77
85
  ]
@@ -15,6 +15,8 @@ count and an unknown operation or channel come back ``unpriced`` with a reason
15
15
  from dataclasses import dataclass
16
16
  from datetime import datetime, timezone
17
17
  from decimal import Decimal, ROUND_HALF_UP
18
+ from collections.abc import Mapping
19
+ from types import MappingProxyType
18
20
  from typing import Any
19
21
 
20
22
  from .catalog import _COST_QUANTUM, _coerce_datetime
@@ -52,6 +54,97 @@ class RetrievalCostResult:
52
54
  reasons: tuple[str, ...] = ()
53
55
 
54
56
 
57
+ # The retrieval operation a counted search or fetch is billed as on each
58
+ # channel. A channel absent here charges no per-use fee for that operation.
59
+ SEARCH_OPERATION_BY_CHANNEL: Mapping[str, str] = MappingProxyType({
60
+ "openai-api": "web_search",
61
+ "anthropic-api": "web_search",
62
+ "google-api": "google_search_grounding",
63
+ "google-vertex-ai": "google_search_grounding",
64
+ # Perplexity bills a request fee by search context size, which no
65
+ # capture path records; the low tier is the floor every request pays.
66
+ "perplexity-api": "search_request_low",
67
+ })
68
+ FETCH_OPERATION_BY_CHANNEL: Mapping[str, str] = MappingProxyType({
69
+ "anthropic-api": "web_fetch",
70
+ })
71
+
72
+ # The tool-call item types a provider emits for a search or a fetch it ran
73
+ # itself, as the Responses API and the relay record them.
74
+ _SEARCH_TOOL_TYPES = frozenset({"web_search_call"})
75
+ _FETCH_TOOL_TYPES = frozenset({"web_fetch_call"})
76
+ _PERPLEXITY = frozenset({"perplexity", "perplexity-ai"})
77
+
78
+
79
+ def search_operation_for_channel(channel: Any) -> str | None:
80
+ """The retrieval operation a counted search is billed as on ``channel``,
81
+ or ``None`` where the channel charges no per-search fee."""
82
+ return SEARCH_OPERATION_BY_CHANNEL.get(str(channel or "").strip().lower())
83
+
84
+
85
+ def fetch_operation_for_channel(channel: Any) -> str | None:
86
+ """The retrieval operation a counted fetch is billed as on ``channel``,
87
+ or ``None`` where the channel charges no per-fetch fee."""
88
+ return FETCH_OPERATION_BY_CHANNEL.get(str(channel or "").strip().lower())
89
+
90
+
91
+ def _count(value: Any) -> int | None:
92
+ if value is None or isinstance(value, bool):
93
+ return None
94
+ try:
95
+ count = int(value)
96
+ except (TypeError, ValueError, OverflowError):
97
+ return None
98
+ return count if count >= 0 else None
99
+
100
+
101
+ def _tool_items(row: Mapping[str, Any], types: frozenset[str]) -> int | None:
102
+ """Items of the given types among ``tool_calls``: a measured zero when the
103
+ list is there and holds none, ``None`` when there is no list to read."""
104
+ calls = row.get("tool_calls")
105
+ if not isinstance(calls, list):
106
+ return None
107
+ return sum(
108
+ 1
109
+ for call in calls
110
+ if isinstance(call, Mapping) and str(call.get("type") or "").strip().lower() in types
111
+ )
112
+
113
+
114
+ def count_search_units(row: Mapping[str, Any]) -> int | None:
115
+ """The searches one call row made, read from whichever field its capture
116
+ path carries, in the order they are believed: ``web_search_calls`` and
117
+ ``grounding_queries`` (the relay's per-provider counts), Anthropic's
118
+ ``server_tool_use.web_search_requests``, then the ``web_search_call``
119
+ items among ``tool_calls``. A Perplexity call is one search, since
120
+ Perplexity bills every request as one. A field that is present and says
121
+ nothing was searched is a measured zero; ``None`` means no field says."""
122
+ for key in ("web_search_calls", "grounding_queries"):
123
+ count = _count(row.get(key))
124
+ if count is not None:
125
+ return count
126
+ server = row.get("server_tool_use")
127
+ if isinstance(server, Mapping):
128
+ count = _count(server.get("web_search_requests"))
129
+ if count is not None:
130
+ return count
131
+ if str(row.get("provider") or "").strip().lower() in _PERPLEXITY:
132
+ return 1
133
+ return _tool_items(row, _SEARCH_TOOL_TYPES)
134
+
135
+
136
+ def count_fetch_units(row: Mapping[str, Any]) -> int | None:
137
+ """The page fetches one call row made: Anthropic's
138
+ ``server_tool_use.web_fetch_requests``, then the ``web_fetch_call``
139
+ items among ``tool_calls``, with the same measured-zero rule."""
140
+ server = row.get("server_tool_use")
141
+ if isinstance(server, Mapping):
142
+ count = _count(server.get("web_fetch_requests"))
143
+ if count is not None:
144
+ return count
145
+ return _tool_items(row, _FETCH_TOOL_TYPES)
146
+
147
+
55
148
  def _units(value: Any) -> tuple[int | None, str | None]:
56
149
  """Coerce a retrieval unit count under a strict-integer contract.
57
150
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.2.54
3
+ Version: 0.2.55
4
4
  Summary: Reusable MeterGraph catalog and deterministic billing engine
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10