You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(web): one rating everywhere from the stored bayesian score #1545
Refs #1531 (phase P4 of the design Docs/Proposals/2026-10-31-criteria-stars-pros-cons.html; JOY approved D1-D10 with the recommendations on 2026-10-31).
Outcome
Every page and the API show the same product rating: master_products.rating_score at one decimal (D2, D3; design 2.4). The expert score is shown separately with its label; the leaderboard combined score only breaks ties.
Scope
Product page, cards, compare, versus, watchlist, leaderboard, /api/v1/rate/* and the OpenAPI spec read rating_score; display-truth tests follow.
Acceptance
iPhone 16 128GB shows one number on the product page, versus scoreboard and matrix, compare and cards.
Depends on: P2 applied. Database: reads the P2 columns only.
Size: S
Follow-ups from PR #1478 (merged 3648fb0), to do once rating_score exists
/api/v1/rate/versus still breaks ties with the composite and checks a tie at 2 decimals. The screen can show a tie while the API names a leader. Make it use the same one-decimal tie as the page, and keep the composite only as the tiebreak.
The home and compare battle cards, the leaderboard and deals still show composite or persona scores at 2 decimals. Under D3 the composite only breaks ties: show rating_score at 1 decimal, and label any persona score as such.
Small fixes:
The buyer score is rounded twice: 4.249 shows 4.3. Round once.
When neither model has a score, the summary prints "0.0" while the stars say "Chưa có đánh giá". Show "Chưa có đánh giá" in both.
Other callers of resolveCompareEffectiveUserRating: compare, watchlist, deep inspect, catalog toolbar. They follow automatically.
API:/api/v1/rate/* select lists add rating_score. Responses add rating_score_5, and the existing fields stay for one release. server/rate/openapi.ts documents it.
Ranking follows D3:
The leaderboard ranks by rating_score. The composite only breaks ties.
Battle cards, leaderboard rows and the podium show rating_score at 1 decimal.
A persona or criterion view keeps its own score, labelled with the persona or criterion. It is not shown as "the rating".
Versus API tie rule. It can land before the migration. is_tie and overall_winner use the score at one decimal, like the page. The composite and price become the tiebreak only. margin_10 is the gap between the scores as shown, and the OpenAPI description follows. Switching the API's base score to rating_score waits for the column.
Composite or persona scores at 2 decimals on battle cards, the leaderboard and deals. This waits for the column: showing a 1-decimal number that the list is not sorted by would make equal-looking scores rank differently.
Small fixes:
"0.0" when neither model has a score. It can land before: the summary and verdict say "Chưa có đánh giá" like the stars.
Double rounding. I propose no code change before the column. The double rounding goes away when the screen reads rating_score directly. But the column itself is stored at 2 decimals, so a Bayesian 4.249 is stored as 4.25 and shown as 4.3. To round only once, P2 would store numeric(4,3) (or wider) and the screen would round to 1 decimal. A question for the P2 author (PR docs(proposals): dos-me migration for criteria ratings, pros and cons (p2) #1549), not a blocker: the error is at most 0.05 and only near x.x5.
Activity
JOY commentedon Oct 31, 2026
Implementation plan (Web)
I read the P2 migration in PR #1549 (
Docs/Proposals/2026-10-31-dos-me-criteria-ratings.sql):master_products.rating_scoreisnumeric(3,2): Bayesian, k = 3, mu = 4.2, NULL when there are no reviews.rating_weight_sumis also added.Every place that switches to
rating_score(after P2 is applied)There is one reader change, and the rest follows from it:
resolveCompareEffectiveUserRating(lib/rate-domain.ts) readsrating_scorefirst. Fallback touser_rating_weightedstays until the backfill is checked.scoreMasterProductForLeaderboarduserScore5(lib/leaderboard-engine.ts) reads the same helper.Direct readers of
user_rating_weightedoraverage_rating, which move to the helper:smartphones/[slug]/page.tsx):computeBayesianRatingSummaryreads the stored score, keeping the review list only for counts;smartphones/page.tsx,rate-api.ts(the card value and the sort onuser_rating_weighted).page.tsx,hero-products.ts,HeroSearchInspector.leaderboard/page.tsx.server/rate/deals.ts.resolveCompareEffectiveUserRating: compare, watchlist, deep inspect, catalog toolbar. They follow automatically./api/v1/rate/*select lists addrating_score. Responses addrating_score_5, and the existing fields stay for one release.server/rate/openapi.tsdocuments it.Ranking follows D3:
rating_score. The composite only breaks ties.rating_scoreat 1 decimal.display-truthtests follow.The three follow-ups from PR #1478
is_tieandoverall_winneruse the score at one decimal, like the page. The composite and price become the tiebreak only.margin_10is the gap between the scores as shown, and the OpenAPI description follows. Switching the API's base score torating_scorewaits for the column.rating_scoredirectly. But the column itself is stored at 2 decimals, so a Bayesian 4.249 is stored as 4.25 and shown as 4.3. To round only once, P2 would storenumeric(4,3)(or wider) and the screen would round to 1 decimal. A question for the P2 author (PR docs(proposals): dos-me migration for criteria ratings, pros and cons (p2) #1549), not a blocker: the error is at most 0.05 and only near x.x5.Order
fix(web): versus api ties at one decimal and unrated pairs say so (#1551