create mvp path

This commit is contained in:
hamid
2026-08-02 20:01:31 +03:30
parent 72ab290da1
commit fb58ca54e1
203 changed files with 863 additions and 156 deletions
@@ -0,0 +1,85 @@
# 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) |