backend phase 14 & frontend phase 7
This commit is contained in:
@@ -12,6 +12,24 @@ One block per completed backend phase. Newest at the top. Backend lane writes he
|
||||
- **Notes for frontend:** <anything load-bearing>
|
||||
-->
|
||||
|
||||
## backend-phase-14 — Reviews, ratings & patient care records — 2026-07-09
|
||||
- **Shipped:** new `reviews` schema, 4 tables — `Reviews` (`UNIQUE(booking_id)`, `CHECK(rating 1–5)`, guarded
|
||||
`moderation_status`, `IAuditable`), `ReviewTagsMaster` (seeded 5-tag vocab, `UNIQUE(code)`), `ReviewTagLinks`
|
||||
(`UNIQUE(review_id, review_tag_master_id)`), `PatientCareRecords` (encrypted, patient-scoped, `(patient_id,
|
||||
recorded_at)` index). One migration (`ReviewsAndPatientCareRecords`). CQRS: SubmitReview / ModerateReview /
|
||||
AttachReviewTags / ListReviewsForNurse / GetReviewModerationQueue / GetTagAggregates / WritePatientCareRecord /
|
||||
GetPatientHistory + the `RecomputeNurseRating` from-source helper. 8 endpoints across 5 controllers. Added
|
||||
`NurseProfile.SetReviewAggregates` (guarded aggregate write) + `ICustomerProfileRepository.GetUserIdByProfileIdAsync`.
|
||||
- **Contracts:** `dev/contracts/domains/reviews-records.md` + openapi snapshot refreshed (yes).
|
||||
- **Mocked:** `IReviewModerationService` (AI pre-screen — keyword/pass-through) → 🟡 (see reports/mocks-registry.md).
|
||||
- **Gate:** build clean (0 new warnings) / tests green (346 total: 4 identity + 236 foundation + 106 API,
|
||||
incl. 13 new review/care-record handler tests + 4 new API tests).
|
||||
- **Handoff:** backend/handoff/after-backend-phase-14.md
|
||||
- **Notes for frontend:** publish gate — only `published` reviews are ever public/counted; the nurse aggregate
|
||||
recomputes from source on every transition. Care records are patient-scoped + encrypted + strict access
|
||||
(owner/assigned-nurse/admin). Money-free domain. Support alerts stay internal (only `lowRatingAlertId` on the
|
||||
admin queue).
|
||||
|
||||
## backend-phase-13 — Weekly nurse payouts (mocked PAYA/SATNA) — 2026-07-09
|
||||
- **Shipped:** new `payouts` schema, 3 tables — `NursePayoutBatches` (holiday-shifted period/processing dates),
|
||||
`NursePayouts` (net-split CHECK, encrypted `iban_snapshot`, forward-only `PayoutStatus`), `NursePayoutBookingLinks`
|
||||
|
||||
@@ -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 1–5, 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.
|
||||
Reference in New Issue
Block a user