backend phase 14 & frontend phase 7

This commit is contained in:
hamid
2026-07-09 15:30:03 +03:30
parent de53f9d8a6
commit 93cc5ecb98
101 changed files with 12930 additions and 39 deletions
@@ -0,0 +1,56 @@
# Handoff — after backend-phase-14 (Reviews, ratings & patient care records)
**The trust loop and the continuity-of-care loop are live.** A customer leaves **one moderated review per
completed booking**; an admin/moderator publishes/hides/rejects it; the nurse's public rating is recomputed
**from source on every transition** (so hiding a 1-star lowers the count and re-derives the average — no
inflated-after-hide drift); low ratings auto-raise an internal `support_alert`; and nurses author **encrypted,
patient-scoped** clinical notes readable only under a strict clinical-access rule.
## What the frontend (f13-b14) can now build
- **Leave a review** — `POST bookings/{bookingId}/review` `{ rating 15, body?, tagCodes? }` (customer who owns a
completed booking). Returns `{ id, moderationStatus: "pending_moderation", lowRatingAlertRaised }`. The review is
**not public** until an admin publishes it — build the "submitted, awaiting moderation" state.
- **Public nurse reviews** — `GET nurses/{nurseProfileId}/reviews?page=&pageSize=` → `{ aggregate: { averageRating,
publishedCount }, reviews: PagedResult<{ id, rating, body, tagCodes[], createdAt }> }`. **Published only.**
- **Public tag rollup** — `GET nurses/{nurseProfileId}/review_tags` → `{ publishedReviewCount, tags: [{ code,
labelFa, labelEn, count, percentage }] }` ("% punctual"). The seeded vocab (`punctual/professional/clean/kind/
communicative`) is always returned.
- **Tag your own review** — `POST reviews/{reviewId}/tags` `{ tagCodes }` (author or moderator; replaces the set).
- **Admin moderation console** — `GET admin/reviews/moderation_queue?status=&page=&pageSize=` (default
`pending_moderation`; each row carries the linked `lowRatingAlertId` for triage) and
`PATCH reviews/{reviewId}/status` `{ action: publish|hide|reject|unpublish, reason? }` (hide/reject need a reason).
The PATCH returns the recomputed `{ averageRating, totalReviews }`.
- **Patient care records** — `POST patients/{patientId}/care_records` `{ bookingId?, body }` (nurse with a
confirmed booking) and `GET patients/{patientId}/care_records?page=&pageSize=` (owning customer / nurse with a
confirmed booking / admin) → decrypted `{ id, patientId, bookingId, nurseProfileId, nurseName, body, recordedAt }`
newest first. The history is **patient-scoped** — a new nurse taking over reads the whole history.
## Contracts
- **`dev/contracts/domains/reviews-records.md`** — all 8 endpoints, the `moderationStatus`/action enums, tag
codes, DTO shapes, and the care-record **access matrix**.
- **`dev/contracts/openapi/swagger.v1.json`** refreshed — the 8 review/care-record paths are in the snapshot.
## What is mocked (and how it becomes real)
- **`IReviewModerationService`** (new) — AI review pre-screen. `MockReviewModerationService` is a keyword filter:
clean text → a human-review **Flag** by default (so the publish gate holds — reviews land `pending_moderation`);
a banned-word substring → **Reject** (auto-hidden). Config `Seams:ReviewModeration:{AutoApproveClean,BannedWords}`.
Make it real → a text classifier / LLM endpoint (see reports/mocks-registry.md). `ModerateReviewCommand` keeps
decision authority + the human override, so the real impl never touches the handler.
## Load-bearing rules (don't regress)
- **Recompute from source, not delta** — every publish/hide/reject/unpublish re-derives `average_rating`/
`total_reviews` over currently-published reviews (exclude-the-changed-review then fold in its new status), in the
same transaction, then refreshes the search index.
- **Publish gate** — `pending_moderation`/`hidden`/`rejected` are never in a public read and never counted.
- **1:1 per completed booking** — `UNIQUE(booking_id)` + handler pre-check; cross-tenant is a 404.
- **Low rating (≤ config `min_rating_for_support_alert`, default 2)** raises an internal `low_rating` alert —
internal-only, never in a user response (only its id shows on the admin queue).
- **Care records are patient-scoped, encrypted at rest, strict access** — a nurse without a confirmed booking for
the patient is denied read + write.
## Deferred (flagged, not built)
- Two-way (nurse-reviews-customer) double-blind reviews with timed reveal.
- First-class `incidents` entity + ML fraud scoring (manual suspension + `support_alerts` cover it now).
- The ticket system, partner centers, and the admin **support-alert worklist console** → **b15** (this phase only
*raises* alerts).
- `SuspendNurse` / `ResolveSupportAlert` / `FlagConcern` admin actions → b15.