metergraph-core 0.5.2__tar.gz → 0.6.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.
Files changed (23) hide show
  1. {metergraph_core-0.5.2/src/metergraph_core.egg-info → metergraph_core-0.6.1}/PKG-INFO +7 -1
  2. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/README.md +6 -0
  3. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/pyproject.toml +1 -1
  4. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/catalog.py +39 -8
  5. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/data/prices.yaml +41 -0
  6. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/loader.py +2 -0
  7. {metergraph_core-0.5.2 → metergraph_core-0.6.1/src/metergraph_core.egg-info}/PKG-INFO +7 -1
  8. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/MANIFEST.in +0 -0
  9. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/setup.cfg +0 -0
  10. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/__init__.py +0 -0
  11. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/billing.py +0 -0
  12. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/catalog_delta.py +0 -0
  13. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/data/models.yaml +0 -0
  14. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/data/sources.yaml +0 -0
  15. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/extractors.py +0 -0
  16. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/models.py +0 -0
  17. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/refresh.py +0 -0
  18. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/retrieval.py +0 -0
  19. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core/sources.py +0 -0
  20. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core.egg-info/SOURCES.txt +0 -0
  21. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
  22. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/src/metergraph_core.egg-info/requires.txt +0 -0
  23. {metergraph_core-0.5.2 → metergraph_core-0.6.1}/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.5.2
3
+ Version: 0.6.1
4
4
  Summary: Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more.
5
5
  Author: Metergraph
6
6
  License-Expression: Apache-2.0
@@ -194,6 +194,12 @@ lists `input_tokens` or `output_tokens` in `missing_dimensions`.
194
194
 
195
195
  A region the model is not sold in is `unpriced` with nothing missing.
196
196
 
197
+ A price closed with `effective_to` and not replaced is the last known price.
198
+ A call after it ended resolves to that price, stays `priced`, and carries
199
+ `price_ended` in `reasons` (on both `PriceResult` and `CostResult`). The
200
+ applied `price.effective_to` says when it ended. A closed price followed by a
201
+ later one never extends, so a gap before a scheduled price stays `unpriced`.
202
+
197
203
  ## Model registry
198
204
 
199
205
  ```python
@@ -164,6 +164,12 @@ lists `input_tokens` or `output_tokens` in `missing_dimensions`.
164
164
 
165
165
  A region the model is not sold in is `unpriced` with nothing missing.
166
166
 
167
+ A price closed with `effective_to` and not replaced is the last known price.
168
+ A call after it ended resolves to that price, stays `priced`, and carries
169
+ `price_ended` in `reasons` (on both `PriceResult` and `CostResult`). The
170
+ applied `price.effective_to` says when it ended. A closed price followed by a
171
+ later one never extends, so a gap before a scheduled price stays `unpriced`.
172
+
167
173
  ## Model registry
168
174
 
169
175
  ```python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metergraph-core"
3
- version = "0.5.2"
3
+ version = "0.6.1"
4
4
  description = "Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -83,6 +83,8 @@ _BEDROCK_CHANNELS = frozenset({_BEDROCK_CHANNEL, "aws-bedrock-geo"})
83
83
  _BEDROCK_VERSION_SUFFIX = re.compile(r"-v\d+(?::\d+)?$")
84
84
  _BEDROCK_GLOBAL_PREFIX = "global."
85
85
  _GLOBAL_REGION = "global"
86
+ # Reason for a result priced at a price that ended and was not replaced.
87
+ PRICE_ENDED = "price_ended"
86
88
 
87
89
 
88
90
  def _bedrock_fallback_ids(model_key: str) -> tuple[tuple[str, bool], ...]:
@@ -321,6 +323,8 @@ class PriceResult:
321
323
  price: Price | None
322
324
  effective_at: datetime
323
325
  missing_dimensions: tuple[str, ...] = ()
326
+ # Notes that do not make the result partial, such as ``price_ended``.
327
+ reasons: tuple[str, ...] = ()
324
328
 
325
329
 
326
330
  @dataclass(frozen=True, slots=True)
@@ -330,6 +334,8 @@ class ResolvedPrice:
330
334
  canonical_model: str
331
335
  price: Price
332
336
  rules: Mapping[str, Any]
337
+ # The last known price: it ended before the resolved time, unreplaced.
338
+ ended: bool = False
333
339
 
334
340
 
335
341
  def _billed_terms(resolved: ResolvedPrice) -> tuple[Any, ...]:
@@ -611,6 +617,9 @@ def _cost_result(
611
617
  batch=usage.batch,
612
618
  )
613
619
  reasons = [f"missing_{dimension}" for dimension in missing] + reasons
620
+ partial = bool(reasons)
621
+ if resolved.ended:
622
+ reasons = [PRICE_ENDED] + reasons
614
623
  missing += tuple(
615
624
  dimension
616
625
  for dimension in ("input_tokens", "output_tokens")
@@ -620,7 +629,7 @@ def _cost_result(
620
629
  resolved.canonical_model,
621
630
  resolved.price.id,
622
631
  cost,
623
- "partial" if reasons else "priced",
632
+ "partial" if partial else "priced",
624
633
  tuple(dict.fromkeys(reasons)),
625
634
  components=components,
626
635
  publisher=resolved.price.publisher,
@@ -715,18 +724,37 @@ class CatalogSnapshot:
715
724
 
716
725
  def _price_for(
717
726
  self, alias: Alias, at: datetime, region: str | None = None
718
- ) -> Price | None:
727
+ ) -> tuple[Price | None, bool]:
728
+ """The price in effect at ``at``, and whether it is the last known
729
+ price: when no row covers ``at``, the row that applied most recently,
730
+ provided nothing in these scopes starts at or after its end. A
731
+ superseded price, or a gap before a scheduled one, never extends."""
719
732
  at = at if at.tzinfo else at.replace(tzinfo=timezone.utc)
720
733
  candidates = self._prices.get((alias.model_id, alias.pricing_channel), [])
721
- for scope in self._price_regions(alias, region or self._region):
734
+ scopes = self._price_regions(alias, region or self._region)
735
+ for scope in scopes:
722
736
  for price in candidates:
723
737
  if price.region.lower() != scope:
724
738
  continue
725
739
  if price.effective_from <= at and (
726
740
  price.effective_to is None or at < price.effective_to
727
741
  ):
728
- return price
729
- return None
742
+ return price, False
743
+ in_scopes = [price for price in candidates if price.region.lower() in scopes]
744
+ ended = [
745
+ price for price in in_scopes
746
+ if price.effective_to is not None and price.effective_to <= at
747
+ ]
748
+ if not ended:
749
+ return None, False
750
+ # The latest end wins; on a tie, the earlier scope, as above.
751
+ last = max(
752
+ ended,
753
+ key=lambda price: (price.effective_to, -scopes.index(price.region.lower())),
754
+ )
755
+ if any(price.effective_from >= last.effective_to for price in in_scopes):
756
+ return None, False
757
+ return last, True
730
758
 
731
759
  def infer_direct_channel(self, model: Any) -> str | None:
732
760
  """Return the sole direct billing channel known for ``model``.
@@ -791,13 +819,14 @@ class CatalogSnapshot:
791
819
  break
792
820
  if alias is None:
793
821
  return None
794
- price = self._price_for(alias, when, region)
822
+ price, ended = self._price_for(alias, when, region)
795
823
  if price is None:
796
824
  return None
797
825
  return ResolvedPrice(
798
826
  canonical_model=alias.canonical_id,
799
827
  price=price,
800
828
  rules=MappingProxyType({**price.rules, **alias.rules}),
829
+ ended=ended,
801
830
  )
802
831
 
803
832
  def _lookup(
@@ -876,7 +905,7 @@ class CatalogSnapshot:
876
905
  None, None, None, "unpriced", ("no_effective_alias",)
877
906
  )
878
907
  return CostResult(None, None, None, "unpriced", ("unknown_model",))
879
- price = self._price_for(alias, when)
908
+ price, ended = self._price_for(alias, when)
880
909
  if price is None:
881
910
  return CostResult(
882
911
  alias.canonical_id,
@@ -886,7 +915,9 @@ class CatalogSnapshot:
886
915
  ("no_effective_price",),
887
916
  )
888
917
  return _cost_result(
889
- ResolvedPrice(alias.canonical_id, price, {**price.rules, **alias.rules}),
918
+ ResolvedPrice(
919
+ alias.canonical_id, price, {**price.rules, **alias.rules}, ended=ended
920
+ ),
890
921
  at=when,
891
922
  usage=ModelUsage(
892
923
  input_tokens=input_tokens,
@@ -1523,6 +1523,7 @@ models:
1523
1523
  - channel: google-api
1524
1524
  region: global
1525
1525
  effective_from: "2026-03-09"
1526
+ effective_to: "2026-10-21"
1526
1527
  input_per_mtok: 2.00
1527
1528
  output_per_mtok: 12.00
1528
1529
  cache_read_per_mtok: 0.20
@@ -1653,12 +1654,33 @@ models:
1653
1654
  - channel: google-api
1654
1655
  region: global
1655
1656
  effective_from: "2026-05-19"
1657
+ effective_to: "2026-10-08"
1656
1658
  input_per_mtok: 1.50
1657
1659
  output_per_mtok: 9.00
1658
1660
  cache_read_per_mtok: 0.15
1659
1661
  batch_input_per_mtok: 0.75
1660
1662
  batch_output_per_mtok: 4.50
1661
1663
  source_url: https://ai.google.dev/gemini-api/docs/pricing
1664
+ # Routed to gemini-3.6-flash; mirrors its google-api rows.
1665
+ - channel: google-api
1666
+ region: global
1667
+ effective_from: "2026-10-08"
1668
+ effective_to: "2027-01-01"
1669
+ input_per_mtok: 0.75
1670
+ output_per_mtok: 3.75
1671
+ cache_read_per_mtok: 0.075
1672
+ batch_input_per_mtok: 0.375
1673
+ batch_output_per_mtok: 1.875
1674
+ source_url: https://ai.google.dev/gemini-api/docs/changelog
1675
+ - channel: google-api
1676
+ region: global
1677
+ effective_from: "2027-01-01"
1678
+ input_per_mtok: 1.50
1679
+ output_per_mtok: 7.50
1680
+ cache_read_per_mtok: 0.15
1681
+ batch_input_per_mtok: 0.75
1682
+ batch_output_per_mtok: 3.75
1683
+ source_url: https://ai.google.dev/gemini-api/docs/changelog
1662
1684
  - channel: google-vertex-ai
1663
1685
  region: global
1664
1686
  effective_from: "2026-05-19"
@@ -1863,6 +1885,7 @@ models:
1863
1885
  - channel: google-api
1864
1886
  region: global
1865
1887
  effective_from: "2026-04-02"
1888
+ effective_to: "2026-10-21"
1866
1889
  input_per_mtok: 0.00
1867
1890
  output_per_mtok: 0.00
1868
1891
  source_url: https://ai.google.dev/gemini-api/docs/pricing
@@ -2531,6 +2554,7 @@ models:
2531
2554
  - channel: anthropic-api
2532
2555
  region: global
2533
2556
  effective_from: "2024-03-13"
2557
+ effective_to: "2026-10-21"
2534
2558
  input_per_mtok: 0.25
2535
2559
  output_per_mtok: 1.25
2536
2560
  cache_read_per_mtok: 0.03
@@ -2816,6 +2840,7 @@ models:
2816
2840
  - channel: google-api
2817
2841
  region: global
2818
2842
  effective_from: "2026-04-02"
2843
+ effective_to: "2026-10-21"
2819
2844
  input_per_mtok: 0.00
2820
2845
  output_per_mtok: 0.00
2821
2846
  source_url: https://ai.google.dev/gemini-api/docs/pricing
@@ -2997,6 +3022,22 @@ models:
2997
3022
  cache_read_per_mtok: 0.15
2998
3023
  source_url: https://vercel.com/ai-gateway/models/muse-spark-1.3
2999
3024
 
3025
+ - canonical_id: typesafe-ai/jev-1.13.0
3026
+ publisher: typesafe-ai
3027
+ aliases:
3028
+ - {provider: typesafe-ai, alias: typesafe-ai/jev-1.13.0, channel: vercel-ai-gateway}
3029
+ - {provider: vercel, alias: typesafe-ai/jev-1.13.0, channel: vercel-ai-gateway}
3030
+ - {provider: unknown, alias: typesafe/jev-1.13.0, channel: vercel-ai-gateway}
3031
+ - {provider: unknown, alias: jev-1.13.0, channel: vercel-ai-gateway}
3032
+ prices:
3033
+ # Vercel lists an input rate and no output fee for this decision model.
3034
+ - channel: vercel-ai-gateway
3035
+ region: global
3036
+ effective_from: "2026-09-15"
3037
+ input_per_mtok: 0.04
3038
+ output_per_mtok: 0
3039
+ source_url: https://vercel.com/ai-gateway/models/jev-1.13.0
3040
+
3000
3041
  - canonical_id: thinkingmachines/inkling
3001
3042
  publisher: thinkingmachines
3002
3043
  aliases:
@@ -15,6 +15,7 @@ import yaml
15
15
 
16
16
  from .catalog import (
17
17
  Alias,
18
+ PRICE_ENDED,
18
19
  CatalogSnapshot,
19
20
  CostResult,
20
21
  ModelUsage,
@@ -159,6 +160,7 @@ class LoadedCatalog:
159
160
  resolved.price,
160
161
  when,
161
162
  missing,
163
+ (PRICE_ENDED,) if resolved.ended else (),
162
164
  )
163
165
 
164
166
  def calculate_cost(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.5.2
3
+ Version: 0.6.1
4
4
  Summary: Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more.
5
5
  Author: Metergraph
6
6
  License-Expression: Apache-2.0
@@ -194,6 +194,12 @@ lists `input_tokens` or `output_tokens` in `missing_dimensions`.
194
194
 
195
195
  A region the model is not sold in is `unpriced` with nothing missing.
196
196
 
197
+ A price closed with `effective_to` and not replaced is the last known price.
198
+ A call after it ended resolves to that price, stays `priced`, and carries
199
+ `price_ended` in `reasons` (on both `PriceResult` and `CostResult`). The
200
+ applied `price.effective_to` says when it ended. A closed price followed by a
201
+ later one never extends, so a gap before a scheduled price stays `unpriced`.
202
+
197
203
  ## Model registry
198
204
 
199
205
  ```python