Skip to content

fix(compare-per-dollar): clearer axis labels and curve-extension distinction in PNG#395

Merged
Oseltamivir merged 8 commits into
masterfrom
fix/per-dollar-png-axis-and-extension
May 27, 2026
Merged

fix(compare-per-dollar): clearer axis labels and curve-extension distinction in PNG#395
Oseltamivir merged 8 commits into
masterfrom
fix/per-dollar-png-axis-and-extension

Conversation

@Oseltamivir
Copy link
Copy Markdown
Contributor

@Oseltamivir Oseltamivir commented May 27, 2026

Summary

Two readability problems in the indexed performance-per-dollar.png shipped in #392:

  1. Y-axis labels were inconsistent ($0.000 / $9.01 / $18.0 / $27.0) because the formatter picked precision from each tick's magnitude. Now uses a 1/2/5-step "nice axis" with a single precision picked from the step size and applied uniformly.

  2. Curve continued past the rightmost labeled dot with no visual cue, so it wasn't clear whether the trailing values were real or extrapolation. The curve is now split at the matched-interactivity bounds:

    • Solid between the labeled dots (the matched comparison band)
    • Dashed + semi-translucent past the last dot (extending toward each SKU's operating envelope edge, where cost rises steeply)
    • Faint italic endpoint labels mark the actual x-range
    • One-line caption under the axis explains the convention

The asymptote story (cost rises sharply approaching each SKU's pareto edge) is preserved, but now the reader can tell at a glance which portion of the curve is the matched comparison and which is the envelope-edge extension.

Implementation

computeCompareImageRows gains an optional includeTargets: number[] argument. The route passes the three plotted-dot targets so they land as exact samples in the curve — the renderer can then partition target >= matchedMin && target <= matchedMax cleanly without per-segment boundary interpolation. includeTargets outside the interactivity range are dropped, and duplicates of the even grid are deduped.

Validation

  • pnpm typecheck, pnpm lint, pnpm fmt all pass (pre-commit hook ran them too)
  • New unit test file compare-ssr.test.ts covers the includeTargets contract: even-grid count preserved, includes are inserted, out-of-range dropped, dedupe works, degenerate range returns empty
  • Fetched the rendered PNG locally with E2E_FIXTURES=1 pnpm dev for three representative pairs (DeepSeek V4 Pro B200 vs MI355X, DeepSeek R1 B200 vs H200, DeepSeek V4 Pro GB200 vs MI355X) — y-axis ticks are clean round numbers, dashed extensions are unambiguous, caption renders within the panel

Before / after on the reported case

Before (#392, DeepSeek V4 Pro B200 vs MI355X): $0.000 / $9.01 / $18.0 / $27.0, curve continues past 42 with no x-tick or styling change.
After: round $0 / $5 / $10 / $15 / $20 / $25 ticks, solid curve between the dots, dashed continuation past the last dot, italic endpoint labels at the curve's actual x-range.


Note

Low Risk
Changes are limited to static OG image rendering, structured data, and sampling helpers—no auth, payments, or core API behavior.

Overview
Improves the performance-per-dollar OG PNG and tightens compare SEO in the same pass.

The PNG route now renders at native 2× resolution (no CSS scale blur), uses d3-style “nice” Y-axis ticks with uniform dollar formatting, and draws cost curves as solid in the matched-interactivity band and dashed toward each SKU’s envelope edges, with faint endpoint labels and a short caption. computeCompareImageRows accepts optional includeTargets so table dot targets are exact samples for clean segment boundaries; vitest covers merge, clamp, and dedupe behavior.

Both /compare and /compare-per-dollar slug pages emit extra BreadcrumbList JSON-LD, pass benchmark datePublished/dateModified into the main graph, and enrich the shared buildJsonLd output (license, keywords, JSON DataDownload link, Thing list items, per-row Dataset parts).

Reviewed by Cursor Bugbot for commit f501dbb. Bugbot is set up for automated code reviews on this repo. Configure here.

…inction in PNG

The indexed performance-per-dollar PNG had two readability problems:

1. Y-axis labels were inconsistent ($0.000 / $9.01 / $18.0 / $27.0)
   because the per-tick formatter picked precision from each tick's
   magnitude. Switched to a 1/2/5-step nice-axis with one precision
   chosen from the step size, applied uniformly to every tick.

2. The curve continued past the rightmost labeled dot with no visual
   distinction, leaving readers unsure whether the trailing values were
   real data or extrapolation. The curve is now split at the matched-
   interactivity bounds: the segment between the labeled dots stays
   solid, and the portion extending toward each SKU's operating
   envelope edge renders as dashed, semi-translucent. Faint italic
   endpoint labels mark the actual x-range, and a one-line caption
   under the axis explains the convention.

`computeCompareImageRows` now accepts an optional `includeTargets`
array so the dot targets are guaranteed to be exact samples in the
curve — the partition into solid / dashed segments connects cleanly
at the boundary without per-segment boundary interpolation in the
renderer.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview, Comment May 27, 2026 3:54am

Request Review

…lyphs

The wrapper used `transform: scale(2)` to upsample a 1200x675 layout into
the 2400x1350 ImageResponse. Satori rasterizes text glyphs at the source
size before applying CSS transforms, so the bitmap got upscaled and the
chart came out blurry. Multiplying every pixel constant by R=2 and
rendering the wrapper at full size keeps strokes and text as native
high-res vectors.
Google's Product rich-result validation requires offers/review/aggregateRating, none of which apply to benchmark subject GPUs. Switch the per-GPU item type to schema.org Thing — vendor/category move into additionalProperty so no crawl info is lost — and the validator warning goes away.
Schema.org Dataset.hasPart expects a CreativeWork (or subclass). Observation isn't a CreativeWork, so Google's structured-data validator rejected each row. Dataset accepts variableMeasured exactly like Observation, so the inner shape is unchanged — only the @type label moves.
Google's Rich Results validator flags Dataset entries without a license URL and a creator. The repo (github.com/SemiAnalysisAI/InferenceX) is Apache-2.0 and the author is SemiAnalysis — use the canonical Apache license URL and the existing AUTHOR_NAME / AUTHOR_URL seo constants so the dataset card is eligible for rich-result rendering.
…crumbs

Three SEO-only additions to the compare slug pages:

1. Dataset metadata polish — datePublished / dateModified derived from the
   benchmark row dates for this pair, plus keywords, isAccessibleForFree,
   and measurementTechnique. These are the fields Google Dataset Search
   actually surfaces.
2. distribution: DataDownload — points at /api/v1/benchmarks?model=… so the
   Dataset isn't just described, it's downloadable in machine-readable form.
3. BreadcrumbList JSON-LD on every /compare/[slug] and /compare-per-dollar/
   [slug] page (Home → GPU Comparisons / GPU Performance per Dollar →
   A vs B (Model)). Drives the Google SERP breadcrumb trail.

No behavioral changes to the UI — only the SSR JSON-LD payload grows.
… name

H100 vs H200 was producing 'NVIDIA, NVIDIA' in the Dataset keywords list, and MI300X vs MI325X was producing 'AMD, AMD'. Wrap the keyword list in a Set so each value appears at most once. Cross-vendor pairs still get both 'NVIDIA, AMD'.
@Oseltamivir Oseltamivir merged commit aa3262d into master May 27, 2026
18 checks passed
@Oseltamivir Oseltamivir deleted the fix/per-dollar-png-axis-and-extension branch May 27, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant