diff --git a/METHODOLOGY.md b/METHODOLOGY.md index 7617d67..5d25d37 100644 --- a/METHODOLOGY.md +++ b/METHODOLOGY.md @@ -484,7 +484,9 @@ Q_i = mean of q_{i,h} over the three forwards A provider's weight also reflects whether it shows up. Each scheduled observation marks every provider: 1 if it was read successfully and produced a price (a price held out by the outlier check of section 6.4 still counts as present when the provider's own vote priced the index, since the fence keeps a print out of the index, not out of the attendance record; when the fence-reject carry below substituted the vote instead, the provider counts absent — its receipt carries a `carried_vote_from` marker), 0 if it was read successfully and produced none, and unchanged if our own collection or parsing failed, since a provider is never penalized for our failure. -The attendance factor `A_i` is the exponentially weighted average of this series over the 90-day regression window, with its own attendance half-life, normalized so a provider present throughout has `A_i` = 1. A newly seated provider's scheduled observations before it joined count as 0, so its first print starts near zero; at the 6-hour half-life, sustained printing reaches full attendance in about two days. +On population-scaled marketplace lanes, a trusted thin-book print contributes its disclosed `population_scale` instead of 1. The scale is `min(machines/min_machines, hosts/min_hosts, 1)`, strictly positive and at most 1; `population_machines` and `population_hosts` describe that print's book. An absent scale means exactly 1. Carried re-casts never borrow the booked print's scale or create a new attendance credit. + +The attendance factor `A_i` is the exponentially weighted average of this series over the 90-day regression window, with its own attendance half-life, normalized so a provider present at full credit throughout has `A_i` = 1. A newly seated provider's scheduled observations before it joined count as 0, so its first print starts near zero; at the 6-hour half-life, sustained printing reaches full attendance in about two days. The missing print itself is handled by cause: diff --git a/README.md b/README.md index 5330639..8c273b9 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ Before public launch, it selects the launch version. Each result reports the version and `methodology_id`; observations before that version's effective time are labeled `back-calculated`. -The raw inputs are disclosed prices and dispersions, recorded currency and FX, +The raw inputs include a trusted print's disclosed `population_scale` attendance +credit (exactly 1 when absent), alongside prices and dispersions, recorded currency and FX, upstream status, carry basis, filter verdicts, timing, top-level flags, and `calc_params`. On vote-pre-smoothing generations (`calc_params.pre_smoothing_half_life_hours`, effective 2026-09-14) each voting diff --git a/scripts/verify_published_record.py b/scripts/verify_published_record.py index fd37b64..732e907 100644 --- a/scripts/verify_published_record.py +++ b/scripts/verify_published_record.py @@ -215,8 +215,8 @@ def _run_full( else "" ) print( - f" FIRST DIVERGENCE: {check.observed_at}{source} " - f"{divergence.quantity} derived {divergence.derived!r} " + f" FIRST DIVERGENCE: {check.observed_at} " + f"{divergence.quantity}{source} derived {divergence.derived!r} " f"published {divergence.published!r}" ) # Same summary shape as the receipts path below: the full re-derivation diff --git a/src/gpu_index/index/weights.py b/src/gpu_index/index/weights.py index 1e96f59..14d4083 100644 --- a/src/gpu_index/index/weights.py +++ b/src/gpu_index/index/weights.py @@ -146,15 +146,23 @@ def new_weight_state() -> Dict[str, Any]: } -def series_print(usd: Any, observation: Tuple[float, str]) -> Dict[str, Any]: +def series_print( + usd: Any, observation: Tuple[float, str], *, credit: Optional[float] = None +) -> Dict[str, Any]: """The ONE constructor for a weight-series price entry: a slot's resolved USD print plus the trusted filter_observation value/currency (native terms, the recorded-currency posture). resolve_slot_prints builds every entry through here and the artifact pins the result verbatim (weight_calc.slot_prints), so the series shape is structurally — not - just test-enforced — identical between the live path and replay.""" + just test-enforced — identical between the live path and replay. + + A thin book's disclosed population scale is fractional attendance credit. + Absent credit means exactly 1.0 and leaves legacy entries unchanged.""" native_price, native_currency = observation - return {"usd": usd, "native": native_price, "currency": native_currency} + entry = {"usd": usd, "native": native_price, "currency": native_currency} + if credit is not None: + entry["credit"] = float(credit) + return entry def _ordinal(day: str) -> int: @@ -1356,7 +1364,8 @@ def compute_attendance_view( A_i = sum_{s not skip} w(s)*present_i(s) / sum_{s not skip} w(s) - where present_i(s) = 1 exactly when s sits in the source's + where present_i(s) is the print's credit (exactly 1 when absent) + when s sits in the source's weight-state PRICES series (the trusted-print presence record -- accepted, sigma-fenced, and mismatch-pending prints alike), state-3 stamps (events code "sk") drop from numerator AND denominator (the @@ -1459,7 +1468,7 @@ def compute_attendance_view( continue denominator += w if s in series: - numerator += w + numerator += w * float(series[s].get("credit", 1.0)) factor = 1.0 if denominator == 0.0 else numerator / denominator # The backward walk (docstring): skips consume nothing; the # verdict LATCHES where the law resolves it, and the walk then @@ -2071,11 +2080,14 @@ def advance_panel_weight_state( prices = weight_state.setdefault("prices", {}) for sid in sorted(prints or {}): entry = prints[sid] - prices.setdefault(sid, {})[obs_stamp] = { + stored = { "usd": entry["usd"], "native": entry["native"], "currency": entry["currency"], } + if "credit" in entry: + stored["credit"] = float(entry["credit"]) + prices.setdefault(sid, {})[obs_stamp] = stored vectors = weight_state.setdefault("vectors", {}) if vector: vectors[obs_stamp] = dict(vector) diff --git a/src/gpu_index/published/full.py b/src/gpu_index/published/full.py index fc2b44a..7e6f986 100644 --- a/src/gpu_index/published/full.py +++ b/src/gpu_index/published/full.py @@ -20,6 +20,7 @@ compute_attendance_view, compute_panel_weights, new_weight_state, + series_print, ) from gpu_index.published.artifacts import PublishedRecordError from gpu_index.published.verify import ( @@ -309,11 +310,15 @@ def public_weight_print(receipt: dict, *, observed_at: str) -> dict: f"{observed_at} {source_id}: the public FX rate is not a " "positive finite number", ) - return { - "usd": float(price), - "native": native, - "currency": currency, - } + credit = receipt.get("population_scale") + if "population_scale" in receipt and ( + not _is_number(credit) or not 0 < credit <= 1 + ): + raise FullReproductionRefusal( + "invalid_population_scale", + f"{observed_at} {source_id}: population_scale must be finite in (0, 1]", + ) + return series_print(float(price), (native, currency), credit=credit) def _first_divergence( @@ -478,7 +483,7 @@ def reproduce_full_history( ) -> FullReproduction: """Derive target-day weights, votes, IQM, and index from raw public rows. - Optional comparison rows supply published outputs only; every derivation + Optional comparison rows supply published final outputs only; every derivation input and state transition still comes from the version history. """ history = sorted(list(observations), key=_stamp) @@ -692,7 +697,10 @@ def reproduce_full_history( published_value = comparison.get("value_usd_gpu_hr") published_band = comparison.get("stability_band_usd_gpu_hr") divergence = _first_divergence( - comparison["receipts"], + # Versioned receipts describe this replay's intermediate + # outputs. As-published receipt copies can be absent or stale; + # only their immutable final values remain the target. + receipts, block, derived_weights, derived_value=derived_value, diff --git a/tests/fixtures/cross_repo/h100-v16-public.observation.json b/tests/fixtures/cross_repo/h100-v16-public.observation.json new file mode 100644 index 0000000..25d94ba --- /dev/null +++ b/tests/fixtures/cross_repo/h100-v16-public.observation.json @@ -0,0 +1,616 @@ +{ + "calc_params": { + "aggregation": "median_ci_votes", + "carry_forward_window_hours": 72, + "collection_interval": "15-minute", + "composite_statistic": "median_ci_votes", + "eligible_tiers": [ + "on-demand" + ], + "filter_sigma": 3, + "filter_sigma_floor_pct": 3, + "filter_terms": "recorded_currency", + "filter_warmup_observations": 10, + "filter_window_observations": 20, + "fx_max_staleness_days": 7, + "fx_source": "ECB", + "index_recomputation": "15-minute", + "iqm_alpha": 0.16666, + "liveness": { + "attendance_eta": 0.5, + "attendance_floor": 0.5, + "attendance_half_life_hours": 6, + "fence_reject_carry": true, + "forward_horizons_hours": [ + 6, + 24, + 48 + ], + "gamma": 4, + "half_life_days": 30, + "history_days": 90, + "lookback_horizons_hours": [ + 6, + 24, + 48 + ], + "max_abs_log_return": 0.5, + "min_train_samples": 10, + "no_price_exclusion_hours": 24, + "ridge_lambda": 1, + "scheme": "predictive_v1", + "switch_min_eligible": 5, + "target_variance_floor": 1e-12, + "weight_max": 0.3, + "weight_min": 0.025 + }, + "manual_exclusions": [], + "manual_verify_pct": 15, + "members": [ + { + "opening_weight": 0.058823529411764705, + "source_id": "civo" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "coreweave" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "crusoe" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "digitalocean" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "hyperbolic" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "hyperstack" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "lambda" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "lium" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "massedcompute" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "nebius" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "runpod" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "scaleway" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "tensorpool" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "together" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "vast" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "verda" + }, + { + "opening_weight": 0.058823529411764705, + "source_id": "voltagepark" + } + ], + "min_sources_to_publish": 5, + "minimum_panel_members_to_record": 5, + "pre_smoothing_half_life_hours": 1, + "vote_sigma_floor_pct": 3, + "vote_sigma_source": "dw_history" + }, + "generated_at": "2026-09-16T00:02:19Z", + "input_snapshot_sha256": "609bb30190d7ec21ecf02327ae728ffde6789db60a8da85ac0ab5fd58fb36c77", + "kind": "gpu_price_index_observation", + "methodology_id": "h100_sxm_v1_calc_v16", + "observed_at": "2026-09-16T00:00:00.000Z", + "reason": null, + "receipts": [ + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.12147601, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 2.99, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.0897, + "sku_identifier": "NVIDIA H100 SXM", + "smoothed_vote_usd": 2.99, + "source_id": "civo", + "source_url": "https://www.civo.com/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061822 + }, + { + "attendance_factor": 1, + "attendance_printed": 1840, + "attendance_ratio": 0.946502058, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 8, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.120398139, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 6.155, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": "NORTH AMERICA", + "sd": 0.18465, + "sku_identifier": "NVIDIA HGX H100", + "smoothed_vote_usd": 6.155, + "source_id": "coreweave", + "source_url": "https://www.coreweave.com/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061664 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.12155237, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.9, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.117, + "sku_identifier": "NVIDIA H100 80GB HGX", + "smoothed_vote_usd": 3.9, + "source_id": "crusoe", + "source_url": "https://www.crusoe.ai/cloud/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": 80, + "weight": 0.061834 + }, + { + "attendance_factor": 1, + "attendance_printed": 1937, + "attendance_ratio": 0.996399177, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121229593, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 4.41, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.1323, + "sku_identifier": "NVIDIA H100", + "smoothed_vote_usd": 4.41, + "source_id": "digitalocean", + "source_url": "https://docs.digitalocean.com/products/droplets/details/pricing/", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061786 + }, + { + "attendance_factor": 0.973314194, + "attendance_printed": 1576, + "attendance_ratio": 0.810699588, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": null, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.020546642, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.19, + "price_disclosure": "published", + "provider_class": "marketplace", + "region": null, + "sd": 0.284457, + "sku_identifier": null, + "smoothed_vote_usd": 3.206008, + "source_id": "hyperbolic", + "source_url": "https://api.hyperbolic.ai/v2/alpha/on-demand/rental-options", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.049261 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121493393, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.2, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": "EU-heavy", + "sd": 0.096, + "sku_identifier": "NVIDIA H100 SXM", + "smoothed_vote_usd": 3.2, + "source_id": "hyperstack", + "source_url": "https://www.hyperstack.cloud/gpu-pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061825 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 8, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121560054, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.99, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.1197, + "sku_identifier": "NVIDIA H100 SXM", + "smoothed_vote_usd": 3.99, + "source_id": "lambda", + "source_url": "https://lambda.ai/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061835 + }, + { + "attendance_factor": 0.885681905, + "attendance_printed": 1785, + "attendance_ratio": 0.918209877, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": null, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.045221043, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 1.39, + "price_disclosure": "published", + "provider_class": "marketplace", + "region": null, + "sd": 0.556185, + "sku_identifier": null, + "smoothed_vote_usd": 1.401888, + "source_id": "lium", + "source_url": "https://lium.io/api/executors", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.050544 + }, + { + "attendance_factor": 1, + "attendance_printed": 1937, + "attendance_ratio": 0.996399177, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.118824423, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 2.89, + "price_disclosure": "published", + "provider_class": "direct_partnered", + "region": null, + "sd": 0.0867, + "sku_identifier": "H100 SXM5 (80GB)", + "smoothed_vote_usd": 2.89, + "source_id": "massedcompute", + "source_url": "https://vm.massedcompute.com/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": 80, + "weight": 0.061434 + }, + { + "attendance_factor": 1, + "attendance_printed": 1932, + "attendance_ratio": 0.99382716, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121839729, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.85, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.1155, + "sku_identifier": "NVIDIA HGX H100", + "smoothed_vote_usd": 3.85, + "source_id": "nebius", + "source_url": "https://nebius.com/prices", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061876 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.09429481, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.49, + "price_disclosure": "published", + "provider_class": "neocloud", + "region": "global", + "sd": 0.1047, + "sku_identifier": "NVIDIA H100 80GB HBM3", + "smoothed_vote_usd": 3.49, + "source_id": "runpod", + "source_url": "https://api.runpod.io/graphql", + "status": "ok", + "upstream_status": "ok", + "vram_gb": 80, + "weight": 0.058029 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "EUR", + "filter_verdict": "accepted", + "fx_rate": 1.1592, + "gpu_count_basis": 8, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.12230419, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.670375, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": "fr-par-2", + "sd": 0.110111, + "sku_identifier": "H100-SXM", + "smoothed_vote_usd": 3.670375, + "source_id": "scaleway", + "source_url": "https://api.scaleway.com/instance/v1/zones/fr-par-2/products/servers", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.061944 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121395116, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 1.99, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.0597, + "sku_identifier": "H100 SXM", + "smoothed_vote_usd": 1.99, + "source_id": "tensorpool", + "source_url": "https://tensorpool.dev/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.06181 + }, + { + "attendance_factor": 1, + "attendance_printed": 1219, + "attendance_ratio": 0.627057613, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.097846616, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.99, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.1197, + "sku_identifier": "NVIDIA HGX H100", + "smoothed_vote_usd": 3.99, + "source_id": "together", + "source_url": "https://www.together.ai/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.058501 + }, + { + "attendance_factor": 0.533629955, + "attendance_printed": 1645, + "attendance_ratio": 0.846193416, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": null, + "gpu_variant": null, + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.015025008, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 4.55725, + "price_disclosure": "published", + "provider_class": "marketplace", + "region": null, + "sd": 1.802965, + "sku_identifier": null, + "smoothed_vote_usd": 4.602335, + "source_id": "vast", + "source_url": "https://console.vast.ai/api/v0/bundles/", + "status": "ok", + "upstream_status": "ok", + "vram_gb": null, + "weight": 0.042571 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.118962839, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 3.266, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": "EU (Nordic DCs)", + "sd": 0.09798, + "sku_identifier": "1x H100 SXM5 80GB on-demand", + "smoothed_vote_usd": 3.266, + "source_id": "verda", + "source_url": "https://verda.com/pricing", + "status": "ok", + "upstream_status": "ok", + "vram_gb": 80, + "weight": 0.061454 + }, + { + "attendance_factor": 1, + "attendance_printed": 1940, + "attendance_ratio": 0.997942387, + "attendance_scheduled": 1944, + "currency": "USD", + "filter_verdict": "accepted", + "fx_rate": null, + "gpu_count_basis": 1, + "gpu_variant": "SXM", + "last_seen": "2026-09-16T00:00:00.000Z", + "liveness_score": 0.121395116, + "no_price_excluded": false, + "no_price_streak": 0, + "price": 1.99, + "price_disclosure": "published", + "provider_class": "direct_principal", + "region": null, + "sd": 0.0597, + "sku_identifier": "h100-sxm5-80gb", + "smoothed_vote_usd": 1.99, + "source_id": "voltagepark", + "source_url": "https://cloud-api.voltagepark.com/api/v1/bare-metal/locations", + "status": "ok", + "upstream_status": "ok", + "vram_gb": 80, + "weight": 0.06181 + } + ], + "restatements": [], + "schema_version": 1, + "sku": "H100", + "stability_band_usd_gpu_hr": 0.555356, + "status": "ok", + "unit": "USD/GPU/hour", + "value_usd_gpu_hr": 3.476907 +} diff --git a/tests/unit/test_public_h100_pin.py b/tests/unit/test_public_h100_pin.py new file mode 100644 index 0000000..72c3bdc --- /dev/null +++ b/tests/unit/test_public_h100_pin.py @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Computable +"""Live H100/v7 public receipt pin, distinct from producer fixtures. + +Fetched 2026-09-19 from: +https://data.getcomputable.com/H100/v7/observations/2026/09/16.json +The envelope was digest-verified before extracting its 00:00 observation: +7e44977acf4bcce3b9587e8e2d256ad21f320e4237d28291d2fc8a8044d16fc0. +The captured row predates population-credit disclosure; never synthesize it +into the live pin. Full-history credit restoration has separate replay tests. +""" +import json +from pathlib import Path + +from gpu_index.published.full import _first_divergence, public_weight_print +from gpu_index.published.verify import recompute_observation + +PIN = Path(__file__).resolve().parents[1] / "fixtures/cross_repo/h100-v16-public.observation.json" + + +def test_live_public_h100_pin_and_full_mode_diagnostic(): + row = json.loads(PIN.read_text()) + assert row["methodology_id"] == "h100_sxm_v1_calc_v16" + assert row["observed_at"] == "2026-09-16T00:00:00.000Z" + check = recompute_observation(row) + assert check.verdict == "match" + assert (check.recomputed_value, check.recomputed_band) == (3.476907, 0.555356) + weights = {r["source_id"]: r["weight"] for r in row["receipts"]} + sources = {r["source_id"]: {"attendance_factor": r["attendance_factor"], + "Q": r["liveness_score"]} for r in row["receipts"]} + for receipt in row["receipts"]: + assert "population_scale" not in receipt + assert "credit" not in public_weight_print(receipt, observed_at=row["observed_at"]) + weights["vast"] = 0.047940 + divergence = _first_divergence( + row["receipts"], {"sources": sources}, weights, + derived_value=3.478314, published_value=row["value_usd_gpu_hr"], + derived_band=0.578014, published_band=row["stability_band_usd_gpu_hr"], + ) + assert (divergence.quantity, divergence.source_id) == ("weight", "vast") + assert (divergence.derived, divergence.published) == (0.047940, 0.042571) diff --git a/tests/unit/test_published_canonical.py b/tests/unit/test_published_canonical.py index 8ced9f3..c3ba1c0 100644 --- a/tests/unit/test_published_canonical.py +++ b/tests/unit/test_published_canonical.py @@ -380,3 +380,16 @@ def test_unknown_kind_refuses(): def test_invalid_utf8_refuses(): with pytest.raises(PublishedRecordError, match="UTF-8"): decode_and_verify_artifact(b'{"a": "\xff"}') + + +def test_population_receipt_additions_remain_digest_verified(): + document = json.loads(_load("observations/2026/08/25.json")) + receipt = document["data"]["observations"][0]["receipts"][0] + receipt.update(population_scale=0.333333, population_machines=2, population_hosts=1) + document["artifact_sha256"] = payload_digest( + {key: document[key] for key in ("data", "meta", "license")} + ) + assert decode_and_verify_artifact(json.dumps(document).encode()) == document + receipt["population_scale"] = 0.5 + with pytest.raises(ArtifactDigestError): + decode_and_verify_artifact(json.dumps(document).encode()) diff --git a/tests/unit/test_published_full.py b/tests/unit/test_published_full.py index 6a6c10f..f896aab 100644 --- a/tests/unit/test_published_full.py +++ b/tests/unit/test_published_full.py @@ -380,7 +380,7 @@ def read_day(self, date, *, sku, **kwargs): assert cli.main() == 1 output = capsys.readouterr().out assert ( - "FIRST DIVERGENCE: 2026-09-01T00:00:00.000Z s1 weight " + "FIRST DIVERGENCE: 2026-09-01T00:00:00.000Z weight s1 " "derived 0.2 published 999.0" ) in output @@ -538,7 +538,7 @@ def test_pre_launch_history_selects_launch_version_and_labels_rows(monkeypatch, @pytest.mark.parametrize("field", ["value_usd_gpu_hr", "stability_band_usd_gpu_hr", "weight", "liveness_score", "attendance_factor"]) -def test_full_compares_against_as_published_outputs(field): +def test_full_compares_as_published_final_outputs_and_versioned_intermediates(field): reader = _two_version_reader() reader.published = copy.deepcopy(reader.published) if field in reader.published[1]: @@ -547,7 +547,8 @@ def test_full_compares_against_as_published_outputs(field): reader.published[1]["receipts"][0][field] = 999.0 result = reproduce_published_history(reader, sku="H100", target_date="2026-09-03") assert result.checks[0].verdict == VERDICT_MATCH - assert result.checks[1].verdict == "mismatch" + expected = "mismatch" if field in reader.published[1] else VERDICT_MATCH + assert result.checks[1].verdict == expected assert result.checks[1].derived_value == 6.0 @@ -861,3 +862,80 @@ def test_armed_classifier_reads_a_fence_reject_carried_vote_as_absent(): unarmed = copy.deepcopy(observation) del unarmed["calc_params"]["pre_smoothing_half_life_hours"] assert public_attendance_events(unarmed) == {} + + +@pytest.mark.parametrize("credit", [0.25, 1.0]) +def test_public_weight_print_preserves_disclosed_attendance_credit(credit): + receipt = {"source_id": "vast", "price": 2.0, "currency": "USD", + "population_scale": credit, "population_machines": 2, + "population_hosts": 1} + assert public_weight_print(receipt, observed_at="2026-09-17T00:00Z") == { + "usd": 2.0, "native": 2.0, "currency": "USD", "credit": credit, + } + + +@pytest.mark.parametrize("credit", [None, True, 0, -0.1, 1.1, "0.5", float("nan"), + float("inf")]) +def test_public_weight_print_refuses_invalid_disclosed_credit(credit): + with pytest.raises(FullReproductionRefusal, match="population_scale"): + public_weight_print( + {"source_id": "vast", "price": 2.0, "currency": "USD", + "population_scale": credit}, observed_at="2026-09-17T00:00Z", + ) + + +@pytest.mark.parametrize("carry_kind", ["status", "fence"]) +def test_full_fractional_attendance_uses_own_print_and_ignores_carried_scale(carry_kind): + rows = [_observation() for _ in range(4)] + for hour, row in enumerate(rows): + row["observed_at"] = f"2026-09-01T0{hour}:00:00.000Z" + row["calc_params"]["pre_smoothing_half_life_hours"] = 1 + row["calc_params"]["min_sources_to_publish"] = 4 + for receipt in row["receipts"]: + receipt["smoothed_vote_usd"] = receipt["price"] + rows[0]["receipts"][2]["population_scale"] = 0.25 + carried = rows[1]["receipts"][2] + carried.update(population_scale=0.1, carry_basis="no_price") + if carry_kind == "status": + carried["upstream_status"] = "carried" + else: + carried.update(filter_verdict="rejected", carried_vote_from=rows[0]["observed_at"]) + rows[1]["receipts"][2]["attendance_factor"] = 0.25 + w_old, w_recent = 2 ** (-2 / 6), 2 ** (-1 / 6) + rows[2]["receipts"][2]["attendance_factor"] = round(0.25 * w_old / (w_old + w_recent), 9) + weights = [2 ** (-age / 6) for age in (3, 2, 1)] + rows[3]["receipts"][2]["attendance_factor"] = round( + (weights[0] * 0.25 + weights[2]) / sum(weights), 9, + ) + result = reproduce_full_history(rows, target_date="2026-09-01") + assert [check.verdict for check in result.checks] == [VERDICT_MATCH] * 4 + # Removing the own-print disclosure changes attendance, not prices. + del rows[0]["receipts"][2]["population_scale"] + divergent = reproduce_full_history(rows, target_date="2026-09-01").checks[1] + assert divergent.first_divergence.quantity == "attendance" + assert divergent.first_divergence.source_id == "s2" + assert divergent.first_divergence.derived == 1.0 + + +@pytest.mark.parametrize("field,quantity", [("weight", "weight"), + ("attendance_factor", "attendance")]) +@pytest.mark.parametrize("strip_published_receipts", [True, False]) +def test_full_cli_compares_versioned_receipts_when_published_receipts_empty_or_stale( + monkeypatch, capsys, field, quantity, strip_published_receipts, +): + reader = _two_version_reader() + reader.published = copy.deepcopy(reader.published) + if strip_published_receipts: + for row in reader.published: + row["receipts"] = [] + reader.histories[2][1]["receipts"][0][field] = 999.0 + spec = importlib.util.spec_from_file_location( + "receipt_divergence_cli", REPO_ROOT / "scripts" / "verify_published_record.py", + ) + cli = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cli) + monkeypatch.setattr(cli, "PublishedRecordReader", lambda: reader) + assert cli.main(["--sku", "H100", "--date", "2026-09-03", "--full"]) == 1 + output = capsys.readouterr().out + assert f"FIRST DIVERGENCE: 2026-09-03T19:00:00.000Z {quantity} s0 derived" in output + assert "published 999.0" in output