Skip to content

chore(release): promote dev to main - #1572

Merged
JOY (JOY) merged 12 commits into
mainfrom
dev
Nov 1, 2026
Merged

JOY (JOY) merged 12 commits into
mainfrom
dev

Conversation

@JOY

@JOY JOY (JOY) commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Promote dev to main.

Merge with a merge commit, never squash (Docs/Resources/RELEASE.md).


📌 TL;DR

This PR introduces the Review Submission v2 database migration (Phase P3), adding a new submit_community_review_v2 RPC with strict validation for criterion-based ratings, pros/cons, and verdicts. It also refines the criteria rating aggregation logic to cap individual review weights and fixes a bug in the product condition detector to prevent trade-in offers from misclassifying new items as used.

🎯 Type of Change

  • 🚀 New feature
  • 🐛 Bugfix
  • 🧹 Refactor
  • ⚙️ CI / Configuration

🔍 Changes Walkthrough

File Summary of Changes
.github/workflows/ci.yml Adds a CI step to replay the new test-review-submission-v2-proposal.mjs script in Docker.
.gitignore Un-ignores the new SQL proposal files for the review submission v2 migration.
.gitleaksignore Adds exceptions for false-positive API key detections in review-form-v2.ts.
Docs/Proposals/2026-10-31-dos-me-criteria-ratings.sql Refactor: Modifies the refresh_master_rating_extras logic. It now uses LEAST(1.0, e.w) for the threshold check (shown_sum) to ensure one heavy review doesn't artificially pass the weight sum requirement, while keeping the stored weight_sum as the plain sum. Also improves the refresh trigger condition to check for actual reviews rather than just cached counts.
Docs/Proposals/2026-10-31-dos-me-criteria-ratings.md Documentation: Updates the design doc to clarify the weight sum logic (capped at 1 per review for the threshold).
Docs/Proposals/2026-10-31-dos-me-criteria-ratings-verify.sql Documentation: Updates verification comments to reflect the new weight sum logic.
Docs/Proposals/2026-11-01-dos-me-review-submission-v2.sql New Feature: The core migration. Creates rate.rating_criteria table, helper functions for text normalization/validation (Vietnamese specific), and the submit_community_review_v2 RPC. Handles criterion stars, verdicts, pros/cons, and writes to rate.review_points.
Docs/Proposals/2026-11-01-dos-me-review-submission-v2-rollback.sql New Feature: Rollback script for the v2 migration.
Docs/Proposals/2026-11-01-dos-me-review-submission-v2-verify.sql New Feature: Read-only verification queries for the v2 migration.
Docs/Proposals/2026-11-01-dos-me-review-submission-v2.md Documentation: Detailed design doc for the v2 submission flow, error codes, and rollout plan.
apps/importer/package.json Adds product-points script and includes product-points.test.ts in the test suite.
apps/importer/src/shopee/__tests__/dist-exports.smoke.mjs Adds NonNewListingError to the list of expected exports.
apps/importer/src/shopee/__tests__/entity-matcher.test.ts Bugfix: Adds extensive test cases for detectProductCondition to ensure trade-in offers ("thu cũ đổi mới", "trợ giá") do not misclassify new listings as used.
apps/importer/src/shopee/__tests__/product-points.test.ts New Feature: Unit tests for the new product-points job, verifying weight calculation and point aggregation logic.

📊 Architectural Flow

sequenceDiagram
    participant Client as Web/API
    participant DB as PostgreSQL (rate schema)
    participant RPC as submit_community_review_v2

    Client->>RPC: POST /api/v1/rate/reviews (v2 payload)
    Note over RPC: SECURITY DEFINER<br/>search_path = ''
    
    RPC->>DB: Check auth (authenticated/service_role)
    RPC->>DB: Fetch master product & category
    RPC->>DB: Fetch latest rating_criteria version
    
    alt Invalid Criteria/Verdict/Points
        RPC-->>Client: Error (SQLSTATE 22023, DETAIL: field)
    end

    RPC->>DB: Validate criterion stars (1-5, half steps)
    RPC->>DB: Validate verdict (20-300 chars)
    RPC->>DB: Validate pros/cons (1-5 items, 5-150 chars)
    RPC->>DB: Check for duplicate lines (comparable form)
    RPC->>DB: Check for evasive cons (Vietnamese patterns)
    
    RPC->>DB: Check rate limits (5 reviews/24h)
    RPC->>DB: Check existing review for user/master
    
    RPC->>DB: INSERT INTO rate.reviews
    RPC->>DB: INSERT INTO rate.review_points (bulk)
    RPC->>DB: INSERT INTO rate.review_authors
    RPC->>DB: INSERT INTO rate.review_flags (excluded initially)
    
    RPC-->>Client: JSON Response (review_id, rating_exact, etc.)
Loading

JOY (JOY) and others added 12 commits November 1, 2026 03:45
* fix(proposals): backfill every master with a reviewed listing

The criteria ratings backfill picked masters by the cached
authentic_review_count or user_rating_weighted. A master whose review
weight was written straight to rate.reviews keeps a stale count of 0 and
was skipped, so V6 failed. The DOS.Me replay caught it. The backfill now
also picks any master with a reviewed listing; the cached test stays for
Item variants whose reviews sit on listings with no variant.

The Docker replay zeroes the 256GB master's cached count before the first
apply and checks the backfill still rates it; it fails on the old filter.

Refs #1543

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1560)

* feat(shared-data): cluster review pros and cons into product points

Pure rules for #1546 (design #1531 section 5.1, D9), with no database
access: normalise lines (diacritics kept, filler words dropped, negations joined
to the next word), assign a criterion by keyword, cluster at 60% shared words,
count each writer once by capped trust weight halved every 12 months, and show a
point with two writers and a weight sum of 1.5 or one expert. Up to three per
criterion and five per side in the summary; points silent for 18 months hide.

Refs #1546

* fix(shared-data): product points review findings

- Keywords match without diacritics only on a line typed without any, and
  words whose plain form is common ("đơ", "đo", "chờ", "êm", "cầm", "cam",
  "mắc") are dropped, so "Pin tụt nhanh do cập nhật" stays battery.
- A keyword inside a longer match ("màn" in "màn hình") counts once.
- A line that negates a word the other states never joins its cluster.
- Lines outside 5..150 characters, with no words, no writer, no date or a
  non-finite weight are not counted.
- D9 reads the exact support, not the two-decimal value.

Refs #1546

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st (#1561)

* fix(importer): reject used, like-new and refurbished listings at ingest (#1430)

rate.products stores no condition, so a non-new listing linked to a
published master (exact model_key, variant or a master hint) fed its
lower price into the new device's min_price and the deal radar.

- ingestProduct throws NonNewListingError (code NON_NEW_LISTING) before
  anything is written when the normalizer detects a non-new condition,
  with or without a master hint (option A of #1430, no DDL).
- Trade-in offers for the buyer's old device ("nhận thu máy cũ",
  "thu cũ lên đời", "đổi máy cũ lấy máy mới") no longer read as used,
  so new listings are not dropped for them.
- The error is exported from @repo/crawlers (dist smoke).

* fix(importer): only a non-new title rejects a listing; 2nd Gen is new (#1430 review)

- The ingest check reads the title alone. Description boilerplate such as
  "không bán hàng cũ" or "99% khách hàng hài lòng" would drop new listings,
  so a description hit still only sets needs_review.
- "2nd" counts as used only in "2nd hand": "AirPods Pro 2nd Gen" and
  "iPhone SE 2nd generation" are new devices.
- The trade-in strip also covers "thu máy cũ đổi máy mới" and
  "thu mua máy cũ".

* fix(importer): check the sanitized title; a bare 98% is not a condition (#1430 Codex)

- The normalizer sets title_condition from the sanitized title, and the
  ingest check reads it, so a control character inside "Máy cũ" no longer
  slips a used listing past the check.
- A bare "98%" or "99%" (such as "Chống ồn 98%") no longer reads as
  like-new; a percentage counts only after máy, ngoại hình, hình thức,
  keng or pin, besides the existing mới, đẹp and zin.

* fix(importer): "thu mua điện thoại cũ" is a trade-in offer too (#1430 Codex)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(importer): trade-in titles in any order stay new (#1430)

detectProductCondition read new-device titles such as "Trợ giá lên đời
máy cũ", "Lên đời máy cũ trợ giá" or "Hỗ trợ đổi máy cũ" as used, so
ingest threw NON_NEW_LISTING on new listings.

- The trade-in strip now takes a trade-in verb (thu, thu mua, đổi, lên
  đời, trợ giá, hỗ trợ) right before "(máy|điện thoại|đt) cũ", with a few
  filler words allowed, or "cũ" right before "lên đời" or "đổi/lấy/sang
  mới".
- Fixtures: nine trade-in titles stay new, and five real used titles stay
  used.

Refs #1430

* fix(importer): keep warranty swaps used; allow amounts and hyphens (#1430 review)

- "Mới" followed by a number or a warranty term ("đổi mới 99%", "đổi mới
  trong 30 ngày") is not a trade-in, so those used listings stay used or
  like-new.
- A bare "hỗ trợ" is no longer a trade-in verb; "hỗ trợ thu/đổi" still is.
- "lại", "giá cao" and an amount ("2 triệu") may sit between the verb and
  the device, and a hyphen, colon or comma may separate the words.

Refs #1430

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(proposals): dos-me review submission v2 rpc (p3)

The DOS-Me migration P3 needs, as a proposal: rate.submit_community_review_v2
checks a Rate review under D5 in the database (criterion stars in half steps,
the required criterion, at most two left out, a 20 to 300 character verdict,
one to five pros and cons, up to three per criterion, no dodging con, no line
twice or in both lists) and writes rating_exact, verdict, criteria_version and
the review_points rows in one transaction. rate.rating_criteria holds the
versioned lists. The replay test keeps the lists, the line rules and the dodge
pattern equal to @repo/shared-data. v1 stays unchanged. Nothing is applied.

Refs #1544

* fix(proposals): review v2 rpc review findings

Two fresh-context reviews of the P3 proposal:
- Lines are stored and measured in one form (NFC, no invisible characters,
  trimmed), on both sides, so a stored line always fits the review_points check.
- Both sides strip the same invisible set: every format character, emoji
  variation selectors and the keycap mark. "Không có gì ☺️" was a dodge that
  passed in review-form.ts, and it differed from the SQL.
- The SQL helpers read text under pg_c_utf8, so they do not depend on the
  database locale; verify V7 checks them on the target database.
- Keyword rules cover criterion lines; criterion stars are stored at 0.1.
- The replay test checks the RPC's star on random sets, every invisible code
  point, emoji and decomposed input; verify V2 and V3 cover every helper.

Refs #1544

* fix(proposals): track the review v2 proposal files past .gitignore

Refs #1544

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hind a flag (#1567)

* feat(web): review form v2 with criteria, verdict and pros and cons behind a flag

Refs #1544. The DB-free part of P3 (design #1531, D5): per-category
criteria in half stars with the weighted star beside them, a 20-300
character verdict, 1-5 pros and cons, optional pros and cons per rated
criterion, and a browser draft. The form and POST /api/v1/rate/reviews
check a review with the same reviewV2Errors, per field.

Behind NEXT_PUBLIC_RATE_REVIEW_FORM_V2=1, off by default: with the flag
off the form and the API take the current path unchanged. The API reads
the product's category on the server and computes the star itself; the
v2 RPC does not exist yet (DOS-Me), so submitCommunityReviewV2 is a stub
that never writes and the route answers 503 REVIEWS_UNAVAILABLE.

* fix(web): review findings on form v2 errors, drafts and the v2 api path

Refs #1544. Errors land on the line they name (blank rows are skipped in
the count), the form is keyed per product so a draft never moves, stored
stars outside half steps are dropped, and the pickers carry aria-required.
The API refuses v1 bodies once v2 is on, keeps pros and cons on one line,
checks a criterion's pros against its cons, rejects an empty criterion
entry, and builds the submission in buildReviewV2Submission so a test sees
the computed star. The stub takes the user's client for the real call.

* fix(web): name the duplicate-line message key so gitleaks stops flagging it

Refs #1544. gitleaks read `key: 'v2ErrDuplicate'` as a generic API key:
the rules now use a DUPLICATE_LINE constant, and the two findings in
2f624a1 are listed in .gitleaksignore with the reason.

* fix(web): keep the duplicate-line constant clear of the secret scan

Refs #1544. The constant's own comment and typed declaration still read as
a generic API key. The comment drops the literal and the declaration uses
`as const`; the two findings in 4c05060 join .gitleaksignore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(db): mirror the p2 criteria ratings migrations from dos-me

DOS/DOS.Me#1489 is applied to production (promote DOS/DOS.Me#1494, 09d264ca).
Mirror its two migrations byte for byte into supabase/migrations and regenerate
packages/shared-data/src/database.types.ts, so the web and the importer get
types for rate.review_points, rate.product_points and the new reviews and
master_products columns. Nothing is applied by this change.

Refs #1543

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(importer): write product pros and cons from review lines

The writer part of #1546: a job reads rate.review_points with each
line's review, flag, listing and category, clusters the lines of every master
with aggregateProductPoints and writes the points a page shows to
rate.product_points (method 'code'). Dry run by default; --apply writes as
rate_worker (SUPABASE_RATE_WORKER_KEY), inserting new rows before deleting a
master's old 'code' rows. A user review counts once the analyzer included it;
an expert review counts at least 1 unless the analyzer excluded it.

Refs #1546

* fix(importer): product points count what the rating counts

From a fresh-context review of the writer:
- Lines count only on crawled listings and from crawled or community reviews,
  as the P2 rating does; seed, synthetic, fixture and backfill rows never show.
- Only the writers' own lines (method 'writer') are read for now.
- A flag with a NULL include counts, as COALESCE(include_in_rating, true) does.
- The job signs in as the rate_worker Auth user (publishable key, email and
  password) and refuses any other role, since rate_worker has no API key.
- The header documents one writer per review and the one-runner rule.

Refs #1546

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(web): one rating everywhere from the stored rating_score

Refs #1545 (P4 of design #1531, D3/D4/D9 approved by JOY). P2 is live, so
every page and the API read master_products.rating_score:

- resolveCompareEffectiveUserRating and the leaderboard's userScore5 read
  rating_score first, unrounded, so a page rounds once to 1 decimal
  (4.249 shows 4.2); the older ratings stay as the fallback.
- The product page headline, catalog, home, hero search, deals, the
  catalog sort and the leaderboard order read the same number.
- The leaderboard ranks by the rating as shown, the composite breaking
  ties; an unrated product with an expert score ranks last. Battle cards
  and rows show the rating at 1 decimal; a persona keeps its own score.
- Criterion scores come from criteria_scores: 3 writers to show, 5 to rank
  or weigh a persona (D9); an expert score stands below that.
- The API adds rating_score_5 to products, product, compare, leaderboard
  and versus; OpenAPI documents it.

* fix(web): review findings on the one rating, versus and badges

Refs #1545. The versus scoreboard and API no longer cut the rating to
2 decimals before the page rounds it, weigh personas only on criteria
past the ranking threshold, and fall back to the rating, not the
composite. The reviews section's buyer score is the stored rating the
headline shows. The overall badge shows the rating at 1 decimal; the
expert tab keeps the expert's own score and order. An expert-only
product keeps its persona score. Deals price a point of the rating, and
exports round like the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
rate Ready Ready Preview Nov 1, 2026 5:25pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c8217ff7-ed72-4bf0-8f8e-2fbfc337bc02

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions github-actions Bot added ci CI/CD workflows and pipelines docs Documentation changes area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs area:crawlers area:database labels Nov 1, 2026
@JOY
JOY (JOY) merged commit b5de9f6 into main Nov 1, 2026
24 checks passed

This branch was successfully deployed

1 active deployment
Preview — 0735c7f7 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:crawlers area:database area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs ci CI/CD workflows and pipelines docs Documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant