86 lines
4.8 KiB
Markdown
86 lines
4.8 KiB
Markdown
# reviews — ratings, tags, moderation
|
|
|
|
> Client seam `client/src/services/reviews/` · `USE_REVIEWS_MOCK = false` (**real**) · 8 server ops
|
|
> Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29).
|
|
|
|
Customer reviews of a completed booking, pre-screened and then human-moderated before they are public.
|
|
Clinical care records are a different domain — [patient-records.md](patient-records.md).
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Auth | Verdict |
|
|
| --- | --- | --- | --- |
|
|
| GET | `/api/v1/bookings/{bookingId}/review_eligibility` | `[Authorize]` | wired |
|
|
| GET | `/api/v1/bookings/{bookingId}/my_review` | `[Authorize]` | wired |
|
|
| POST | `/api/v1/bookings/{bookingId}/review` | `[Authorize]` | wired |
|
|
| GET | `/api/v1/nurses/{nurseProfileId}/reviews` | **anonymous** | wired · paginated (**`page`/`pageSize`**) |
|
|
| GET | `/api/v1/nurses/{nurseProfileId}/review_tags` | **anonymous** | **unwired** — the client reads tags off the review rows |
|
|
| PATCH | `/api/v1/reviews/{reviewId}/status` | `[Authorize]` | wired — the moderation decision |
|
|
| POST | `/api/v1/reviews/{reviewId}/tags` | `[Authorize]` | **unwired** — no console screen edits tags |
|
|
| GET | `/api/v1/admin/reviews/moderation_queue` | admin | wired · paginated |
|
|
|
|
No phantoms. Note `PATCH` — one of only two `PATCH` verbs in the whole API (the other is on
|
|
[partner-center.md](partner-center.md)); everything else mutates with `POST`. Note also the hardcoded
|
|
nested `admin/reviews/` route segment — see [index.md](index.md#route-shape-exceptions).
|
|
|
|
## Aggregates are recomputed, never incremented
|
|
|
|
`averageRating`, `totalReviews` and `totalCompletedBookings` on the nurse profile are **recomputed from
|
|
source** whenever a review's moderation status changes. Hiding a published review must lower the average;
|
|
an incrementing counter cannot do that correctly. Never `+= 1` a review aggregate. See
|
|
[profiles.md](profiles.md).
|
|
|
|
## The moderation gate
|
|
|
|
```
|
|
submit ──AI pre-screen──▸ pending_moderation ──admin──▸ published
|
|
│ │
|
|
banned word hit unpublish ──▸ hidden
|
|
▼
|
|
rejected
|
|
```
|
|
|
|
- **A review is not public on submit.** `IReviewModerationService` pre-screens; by default clean text
|
|
returns a human-review **flag**, keeping the gate on. `Seams:ReviewModeration:AutoApproveClean` makes
|
|
clean text auto-publish; `BannedWords` (default `scam`, `fraud`, `کلاهبردار`) forces `reject`. Both are
|
|
mock knobs — a real classifier ignores them.
|
|
- **A low rating raises a support alert** (`low_rating`), linked as `lowRatingAlertId` on the queue item.
|
|
See [admin.md](admin.md).
|
|
- **`unpublish` is a distinct action from `hide`** in the client's `ModerationAction` union even though both
|
|
land on `hidden` — the audit trail records which was chosen.
|
|
|
|
## Shape rules the JSON does not express
|
|
|
|
- **`ReviewEligibilityDto` is `{ canReview, reason }`** and the reason is a **code**, not a message:
|
|
`not_completed` `already_reviewed` `not_owner` `not_found`. The client maps it to an i18n key. Eligibility
|
|
is server-decided — the client must not infer it from booking status.
|
|
- **The author is masked** (REQ-026, confirmed): a public review carries `authorMasked`, never a full name.
|
|
This is a privacy decision, not a display choice.
|
|
- **`tagCodes` is on `ModerationQueueItemDto`** (REQ-037, delivered) so the queue shows what the reviewer
|
|
tagged without a second fetch.
|
|
- **Review tags are codes; labels are i18n keys.** Never render a tag code, and never build a display
|
|
string from one.
|
|
- `moderationReason` is admin-facing free text and is **not** returned on the public review read.
|
|
- The public reviews list is **anonymous and paginated with `page`/`pageSize`** (camelCase — unlike
|
|
`search/nurses`, which uses `page_size`). See [../api-contract.md](../api-contract.md#pagination).
|
|
|
|
## Enums
|
|
|
|
| Vocabulary | Values |
|
|
| --- | --- |
|
|
| `ModerationStatus` | `pending_moderation` `published` `hidden` `rejected` |
|
|
| `ModerationAction` | `publish` `hide` `reject` `unpublish` |
|
|
| `ReviewIneligibilityReason` | `not_completed` `already_reviewed` `not_owner` `not_found` |
|
|
|
|
`ModerationStatus` and `ModerationAction` are both defined in
|
|
`Entities/Reviews/ReviewModerationStatus.cs` and verified identical to the client's unions — the file
|
|
carries the four states and the four actions together.
|
|
|
|
## Open REQs
|
|
|
|
| REQ | Status | Effect |
|
|
| --- | --- | --- |
|
|
| REQ-026 | delivered | Eligibility + my-review-for-booking reads, and masked-author confirmation |
|
|
| REQ-037 | delivered | `tagCodes` on the moderation queue item |
|
|
| REQ-040 | open | No `topReviewTag` on the search index row — the C2 card renders without the tag chip. Owned by [search.md](search.md) |
|