Skip to content

fix(search): type search score as double - #889

Closed
vladjerca wants to merge 1 commit into
masterfrom
align/search-score-double
Closed

fix(search): type search score as double#889
vladjerca wants to merge 1 commit into
masterfrom
align/search-score-double

Conversation

@vladjerca

@vladjerca vladjerca commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Why

The exact-search handler now scores a hit by confidence instead of returning a constant, emitting fractional bands so clients can rank an unambiguous match above fuzzy results and hold the deep catalog tail below them:

band score meaning
certain 1 unique title, or one pinned down by an explicit year
strong 0.75 shares its title but clears the popularity floor
deep 0.25 shares its title, clears no floor

score was annotated int64. That is correct for the fuzzy pass, where the value is a Typesense text_match magnitude at ~1e17 scale, but it cannot represent the bands above.

Generated consumers take the annotation literally. The Kotlin client decoded the field as kotlin.Long, and kotlinx throws on any fractional value:

JsonDecodingException: Unexpected JSON token at offset 10:
Unexpected symbol '.' in numeric literal at path: $.score

Verified by running that decode against the generated model, not inferred.

Change

score on all four search response shapes (movie, show, person, list) moves from int64(z.number().int()) to double(z.number()). format: double still covers the fuzzy magnitudes at the precision JSON already limits them to.

Adds a double helper next to float/int64, and corrects the int64 doc comment, which described itself as a double. int64 stays in use for genuine int64 ids (scrobble, trending).

Verification

  • deno check on the search contract clean
  • deno task openapi:generate + openapi:validate clean; regenerated spec emits {"type":"number","format":"double"} for score on /search/{type}, /search/{type}/exact and /search/{id_type}/{id}
  • Confirmed downstream: regenerating the Android client off the matching spec change yields val score: kotlin.Double and decodes 0.75 / 0.25 / 1
  • deno lint reports 500 pre-existing problems repo-wide (no-explicit-any and friends); none in the touched files

Notes

  • Paired client PRs: fix(app): rank the deep catalog tail after fuzzy results trakt-android#309 (spec mirror + weave), trakt/trakt-apple-internal#301 (weave only; its model already decodes score as Double?).
  • Adjacent, not touched: searchResultResponseSchema is a z.union([...]), which schemas.md calls out as the thing to avoid because codegen turns oneOf into a model with every member's fields required. That is exactly what the Kotlin client shows today. Worth a separate pass.

The exact-search handler now scores a hit by confidence rather than
returning a constant, emitting fractional bands (1 certain, 0.75 strong,
0.25 deep) so clients can rank an unambiguous match above fuzzy results
and hold the deep catalog tail below them.

`score` was annotated `int64`, which is correct for the fuzzy pass (a
Typesense `text_match` magnitude) but cannot represent those bands. A
generated consumer takes the annotation literally: the Kotlin client
decodes the field as `kotlin.Long`, so a fractional score is not
representable there at all.

Widen to `format: double`, which still covers the fuzzy magnitudes at the
precision JSON already limits them to. Adds a `double` helper alongside
`float`/`int64`, and corrects the `int64` doc comment, which described
itself as a double.
@vladjerca

Copy link
Copy Markdown
Contributor Author

Closing this - trakt/trakt-workers#1325 fixed it from the other end, and better.

I widened the contract to fit fractional bands. #1325 made the bands whole numbers instead (certain: 2, strong: 1, deep: 0, threshold: 1), so z.number().int() was right all along and there is nothing to widen. Its deep: 0 also keeps a client bucketing on >= 0.5 correct, which is what the shipped web weave does, so no client needs a second change.

Merging this would have loosened the exact validation #1325 depends on. int64 stays.

Client PRs updated to match: trakt/trakt-android#309 dropped its spec mirror and now compiles against kotlin.Long unchanged; trakt/trakt-apple-internal#301 needed no code change.

One thing worth a separate look, unrelated to the band values: searchResultResponseSchema is a z.union([...]), which schemas.md calls out as the pattern to avoid because codegen turns oneOf into a model with every member's fields required. The Kotlin client shows exactly that today.

@vladjerca vladjerca closed this Jul 27, 2026
@vladjerca
vladjerca deleted the align/search-score-double branch July 27, 2026 23:21
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.

2 participants