Files
baya-monorepo/dev/shared-working-context/backend/handoff/after-backend-phase-14.md
T
2026-07-09 15:30:03 +03:30

57 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.