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
@@ -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 15)`, 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 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.