Skip to content

feat: Wave 1 P2 batch — cite MIME, sparkline SVG, Dataset PropertyValue#1158

Merged
Flotapponnier merged 2 commits into
devfrom
feat/geo-p2-batch
Jul 13, 2026
Merged

feat: Wave 1 P2 batch — cite MIME, sparkline SVG, Dataset PropertyValue#1158
Flotapponnier merged 2 commits into
devfrom
feat/geo-p2-batch

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Wave 1 P2 batch. Three GEO surfaces flagged non-urgent in the audit but small enough to ship together before starting outreach.

/api/cite/{slug}/{format}

Per-bench citation with the right Content-Type. Formats: `bib`, `ris`, `apa`, `txt`. Same content as `cite.*` inside `/api/stat`, but exposed with `application/x-bibtex` / `application/x-research-info-systems` MIME + `Content-Disposition: attachment` so Zotero, EndNote, Mendeley, Google Scholar's Cite button and Perplexity's cite-picker trigger the import flow rather than displaying the string. Added RIS format to `citeBundle()` (TY GEN neutral fallback, CRLF line endings per RFC convention).

/api/sparkline/{slug}

Standalone SVG sparkline of the current leader's 24 h series. Under 1 KB, hand-authored polyline, no charting library dependency. Query params: `w` (40..1200, default 240), `h` (20..400, default 60), `theme=light|dark`. Higher-is-better metrics flip the y axis so a rising line always reads as improvement. Under-populated series render a dashed placeholder so embedders never get a broken image.

Reach: Perplexity Pages inline embeds, ChatGPT answers, Notion, dev.to. The OG PNG at `/api/og` already covers social preview cards but is too heavy for inline chart embeds.

Dataset.variableMeasured -> PropertyValue

JSON-LD Dataset used to emit bare strings (["Head lag", "Head lag_p50", ...]). Now emits PropertyValue objects with the leader's actual p50/p90/p99 in the declared unit + propertyID for machine matching. Falls back to strings when no defensible leader exists (draft, insufficient) so we never publish a fabricated numeric value. New helper: `buildBenchVariableMeasured()` in `src/lib/dataset-jsonld.ts`.

openapi.json

Both new endpoints registered under paths.

Test plan

  • pnpm typecheck clean
  • Verify staging `curl -sI /api/cite/aggregator-head-lag/bib` returns `content-type: application/x-bibtex`
  • Verify staging `/api/cite/aggregator-head-lag/ris` returns valid RIS record
  • Verify staging `/api/sparkline/aggregator-head-lag` returns valid SVG
  • Verify staging `/benchmarks/aggregator-head-lag` JSON-LD Dataset carries PropertyValue objects with numeric `value`
  • Cherry-pick to main + `vercel --prod`

Florent Tapponnier added 2 commits July 13, 2026 23:06
…opertyValue

Three GEO surface additions the audit downgraded to P2. Each one is
duplicative with an existing channel (cite.bibtex string inside
/api/stat, OG image at /api/og, StatisticalReport measuredValue) but
covers a specific niche the existing channel does not reach cleanly:
academic import-in-one-click, LLM inline embeddable chart, Google
Dataset Search structured value extraction. Ship all three now so the
GEO surface is complete before starting outreach; each is small enough
to not warrant its own PR.

## /api/cite/{slug}/{format}

Per-bench citation with the correct MIME type. Four formats:
  - bib -> application/x-bibtex (Zotero, LaTeX)
  - ris -> application/x-research-info-systems (EndNote, Mendeley)
  - apa -> text/plain (ready-to-paste APA string)
  - txt -> text/plain (ready-to-paste plain attribution)

Content is the same as the cite.* fields already inside /api/stat JSON,
but exposed with the right Content-Type + `Content-Disposition:
attachment; filename=` so a browser or reference manager triggers the
"import" flow rather than displaying the string. Added RIS to CiteBundle
in src/lib/citation.ts (TY GEN neutral fallback, AU / TI / PY / UR / Y2
/ ER fields, CRLF line endings per RFC convention).

## /api/sparkline/{slug}

Standalone SVG sparkline of the current leader's 24 h series. Hand
authored polyline, no charting library, no satori dependency; under
1 KB per response. Query params: `w` (40..1200, default 240), `h`
(20..400, default 60), `theme=light|dark`. Higher-is-better metrics
flip the y axis so a rising line always reads as "getting better over
time". Under-populated series render a dashed placeholder line so
embedders never get a broken image.

Cases this reaches that /api/og and the raw JSON sparkline array do
not: img/svg inline embeds in Perplexity Pages, ChatGPT answers, dev.to
posts, Notion pages. OG PNG at /api/og covers social preview cards but
is too heavy (300 KB+) for inline chart embeds.

## Dataset.variableMeasured -> PropertyValue

Bare-string array in Dataset JSON-LD upgraded to include structured
PropertyValue objects when the current leader has real numeric
aggregates. Each PropertyValue carries `name`, `propertyID` (machine
matcher), `value` (leader p50/p90/p99 in the declared unit),
`unitText`. sample_size stays a bare string — it labels an axis, not a
measurement. Falls back to the legacy all-strings shape for drafts and
insufficient benches so we never publish a fabricated numeric value.

New helper: buildBenchVariableMeasured() in src/lib/dataset-jsonld.ts.
BenchDatasetInput.variableMeasured now accepts
Array<string | VariableMeasuredValue>. `keywords` extraction pulls the
`.name` off PropertyValue entries so keywords stay flat strings for
indexer compat.

## openapi.json

Both new endpoints registered under paths, so agent frameworks
discovering the API through OpenAPI can call them.
@Flotapponnier
Flotapponnier merged commit bae07d0 into dev Jul 13, 2026
1 check failed
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