metergraph-core 0.2.0__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.2.0
3
+ Version: 0.2.1
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.0"
3
+ version = "0.2.1"
4
4
  description = "Reusable MeterGraph catalog and deterministic billing engine"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -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:
@@ -84,6 +84,10 @@ class LoadedCatalog:
84
84
  (_normalize_provider(provider), model_id), model_id
85
85
  )
86
86
 
87
+ def infer_direct_channel(self, model: Any) -> str | None:
88
+ """Infer a direct pricing channel only when the catalog is unambiguous."""
89
+ return self.snapshot.infer_direct_channel(model)
90
+
87
91
  def price(
88
92
  self,
89
93
  *,
@@ -150,6 +154,7 @@ def parse_catalog(
150
154
  canonical = str(entry.get("canonical_id") or "")
151
155
  if not canonical:
152
156
  raise CatalogError("model entry missing canonical_id")
157
+ publisher = str(entry.get("publisher") or "").strip().lower() or None
153
158
  for alias in entry.get("aliases") or []:
154
159
  provider = str(alias.get("provider") or "").lower()
155
160
  name = str(alias.get("alias") or "").lower()
@@ -167,6 +172,7 @@ def parse_catalog(
167
172
  canonical_id=canonical,
168
173
  pricing_channel=channel,
169
174
  rules=_freeze(alias.get("rules") or {}),
175
+ publisher=publisher,
170
176
  )
171
177
  seen_windows: list[tuple[str, str, datetime, datetime | None]] = []
172
178
  for price in entry.get("prices") or []:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Reusable MeterGraph catalog and deterministic billing engine
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10