refinement phase 1
This commit is contained in:
@@ -79,6 +79,11 @@ On boot the API applies all EF migrations and seeds roles + an admin user + a sa
|
||||
against the (empty) local DB, then listens on **`https://localhost:5002`** — Swagger at
|
||||
`https://localhost:5002/swagger`.
|
||||
|
||||
In **Development** it additionally runs the **demo-world seeder** (Refinement Phase 1): verified/unverified
|
||||
demo nurses with priced variants + Tehran coverage (and therefore real `nurse_search_index` rows), plus demo
|
||||
customers with patients and addresses. The console logs `Demo world seeded: 3 nurse(s), 2 customer(s)…` (or
|
||||
`already seeded — no-op` on a subsequent run). It is **idempotent** and **never runs outside Development**.
|
||||
|
||||
### Terminal 2 — frontend (from `client/`)
|
||||
|
||||
```bash
|
||||
@@ -93,10 +98,29 @@ Serves **`http://localhost:3000`**. It reads the API base URL from `client/.env.
|
||||
|
||||
---
|
||||
|
||||
## Demo accounts (Development seed)
|
||||
|
||||
The demo seeder creates these loginable accounts (phone-OTP; any 6-digit code you read from the console/dev
|
||||
endpoint works). Use them to see the real path populated:
|
||||
|
||||
| Phone | Role | Who | State |
|
||||
| --- | --- | --- | --- |
|
||||
| `09120000001` | nurse | زهرا عزیزی (female) | **verified**, 3 variants, whole-city + 2 districts |
|
||||
| `09120000002` | nurse | علی کریمی (male) | **verified**, 2 variants, 3 districts |
|
||||
| `09120000003` | nurse | مریم احمدی (female) | **unverified** — not discoverable in search |
|
||||
| `09120000010` | customer | سارا محمدی (female) | 2 patients, 1 Tehran address |
|
||||
| `09120000011` | customer | رضا حسینی (male) | 1 patient, 1 Tehran address |
|
||||
| `admin` / `qw123321` | admin | seeded admin (username+password) | backoffice |
|
||||
|
||||
Prove search works without the frontend: open Swagger →
|
||||
`GET /api/v1/search/nurses?service_category_id=1&city_id=101` returns the two verified nurses' variants;
|
||||
`service_category_id=3` (only the unverified nurse) returns an empty page.
|
||||
|
||||
## Log in (the real round-trip)
|
||||
|
||||
1. Open **`http://localhost:3000/fa/login`**.
|
||||
2. Enter an Iranian mobile number (e.g. `09120000001`) and request the code.
|
||||
2. Enter an Iranian mobile number (e.g. the verified nurse `09120000001`, or any demo phone above) and request
|
||||
the code.
|
||||
3. Get the 6-digit OTP one of two ways:
|
||||
- **Read the server console** (Terminal 1) — SMS is mocked, so the code is logged:
|
||||
`MOCK SMS — OTP code 123456 for phone ending in 0001`.
|
||||
@@ -118,8 +142,18 @@ Serves **`http://localhost:3000`**. It reads the API base URL from `client/.env.
|
||||
automatically, so `fetch` just works; for `curl` add `--http2`.
|
||||
- **Only `auth` is real by default.** 21 of 22 client service domains default to an in-browser mock
|
||||
(`USE_*_MOCK = true`); the home, search, bookings, etc. are fake in-memory data until Refinement Phase 4.
|
||||
- **The DB self-migrates + self-seeds**, so pointing at an empty local instance is enough. Rich demo data
|
||||
(nurses, variants, search rows) arrives in [Refinement Phase 1](refinement-phase-1-database-and-seed.md).
|
||||
- **The DB self-migrates + self-seeds**, so pointing at an empty local instance is enough — including the
|
||||
Development demo world (nurses, variants, search rows, customers) from
|
||||
[Refinement Phase 1](refinement-phase-1-database-and-seed.md).
|
||||
- **Explicit migration path (deploy / migrations-off-boot).** Boot-time `MigrateAsync` is the dev convenience;
|
||||
to apply migrations without booting the app (the direction [Refinement Phase 7](refinement-phase-7-unattended-ops.md)
|
||||
will formalise), run from `server/`:
|
||||
```bash
|
||||
dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api
|
||||
```
|
||||
(`dotnet tool install --global dotnet-ef` if the `ef` command is missing. It reads the same connection
|
||||
string, so set the user-secret / env var first.) This applies **schema only** — the reference + demo seeds
|
||||
run on the next app boot.
|
||||
- **Allowed browser origins** are configuration-driven (`Cors:AllowedOrigins`), defaulting to
|
||||
`http://localhost:3000` in Development. A deployed environment lists its real web origin(s).
|
||||
|
||||
@@ -127,9 +161,13 @@ Serves **`http://localhost:3000`**. It reads the API base URL from `client/.env.
|
||||
|
||||
```bash
|
||||
docker compose down # stop the DB, keep its data
|
||||
docker compose down -v # stop the DB and wipe the volume (fresh migrate + seed next run)
|
||||
docker compose down -v # stop the DB and wipe the volume (fresh migrate + reference + demo seed next run)
|
||||
```
|
||||
|
||||
Re-running `dotnet run` against an **existing** seeded DB does not duplicate anything — both the reference and
|
||||
the demo seeders are idempotent (the demo seeder guards each persona on its phone number). To get a clean demo
|
||||
world, wipe the volume (`docker compose down -v`) and boot again.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|
||||
@@ -12,6 +12,31 @@ One block per completed backend phase. Newest at the top. Backend lane writes he
|
||||
- **Notes for frontend:** <anything load-bearing>
|
||||
-->
|
||||
|
||||
## refinement-phase-1 — Database: local-dev story, demo seed & migration hygiene — 2026-07-13
|
||||
- **Shipped (no migration, no endpoint, no contract change):** Development-gated **demo-world seeder** —
|
||||
`Persistence/Services/Seeding/DemoWorldSeeder.cs` + `DemoWorldDefinitions.cs`, scoped-registered in
|
||||
`AddPersistenceServices`, invoked from `Program.cs` via new `SeedDemoWorldAsync()` **only under
|
||||
`IsDevelopment()`**. Idempotently (guarded on phone) creates **3 nurses** (2 verified: `MarkVerified()` +
|
||||
`nurse_verifications` `approved` + credentials + `matched_national_id` primary bank + 2–3 IRR variants +
|
||||
Tehran areas incl. whole-city `district_id=NULL`; 1 unverified), **2 customers** (patients + coord
|
||||
addresses), and **1 cross-category required demo option group** (شیفت / Shift Type). Search rows are driven
|
||||
through the real `ISearchIndexMaintainer.RebuildAsync` — never hand-inserted, so `is_searchable` stays
|
||||
truthful (verified surface, unverified doesn't). Reference `HasData` seeds + the 17 migrations untouched.
|
||||
- **Local DB / migration hygiene:** verified Phase 0's compose + placeholder connstrings + boot-migrate;
|
||||
documented the explicit `dotnet ef database update` path + demo-reset flow in the RUNBOOK; confirmed no
|
||||
pending model changes. Forward-dep FKs (Phase 6) and the boot-migrate multi-instance split (Phase 7) are
|
||||
out of scope by design.
|
||||
- **Contracts:** none produced; swagger snapshot **not** regenerated (no route/shape change).
|
||||
- **Mocked:** none introduced (the seeder uses the real handlers/maintainer/converters).
|
||||
- **Gate:** build clean (0 new warnings) / tests green (**372**: +3 `DemoWorldSeederTests` proving over the
|
||||
real HTTP pipeline — verified nurse in search, unverified not, trust badge correct, idempotent). Added
|
||||
`InternalsVisibleTo("Baya.Test.Api")` on Persistence for the seeder test.
|
||||
- **Handoff:** backend/handoff/after-refinement-phase-1.md
|
||||
- **Notes for frontend:** demo accounts to log in as (phone-OTP) — nurses `09120000001` (verified) /
|
||||
`09120000002` (verified) / `09120000003` (unverified); customers `09120000010` / `09120000011`. Tehran
|
||||
`city_id=101`. `GET /search/nurses?service_category_id=1&city_id=101` now returns real verified nurses.
|
||||
**No `USE_*_MOCK` flag flipped — de-mocking is Phase 4.**
|
||||
|
||||
## refinement-phase-0 — Local end-to-end bring-up & the integration seam — 2026-07-12
|
||||
- **Shipped (integration/plumbing — no business logic):** **CORS** (`Baya.WebFramework/ServiceConfiguration/
|
||||
CorsServiceExtension.cs` → `AddCorsPolicies`, policy `BalinyaarWebClient` from `Cors:AllowedOrigins`, default
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Handoff — after refinement-phase-1 (Database: local-dev story, demo seed & migration hygiene)
|
||||
|
||||
**Date:** 2026-07-13 · **Track:** backend (+ DB) · **Unlocks:** real search/discovery/booking data for
|
||||
[Refinement Phase 4](../../../post-phase/refinement/refinement-phase-4-frontend-de-mock.md).
|
||||
|
||||
## What the frontend can now do
|
||||
- **A fresh Development DB is a populated marketplace, not an empty shell.** On boot (Development only) the API
|
||||
now seeds a coherent demo world *after* the reference/lookup seeds. Every real-path discovery/search/booking
|
||||
screen has data on the real path — no frontend change required to see it.
|
||||
- **Log in as real demo accounts** (phone-OTP flow; read the code from the server console or
|
||||
`GET /api/v1/dev/last_otp/{phone}`). The accounts already hold the right role, so the role router lands them
|
||||
on the correct shell:
|
||||
|
||||
| Phone | Role | Who | State |
|
||||
| --- | --- | --- | --- |
|
||||
| `09120000001` | nurse | زهرا عزیزی (female) | **verified**, accepting, 3 variants, whole-city + 2 districts |
|
||||
| `09120000002` | nurse | علی کریمی (male) | **verified**, accepting, 2 variants, 3 districts |
|
||||
| `09120000003` | nurse | مریم احمدی (female) | **unverified** (pending) — never surfaces in search |
|
||||
| `09120000010` | customer | سارا محمدی (female) | 2 patients, 1 Tehran address (coords) |
|
||||
| `09120000011` | customer | رضا حسینی (male) | 1 patient, 1 Tehran address (coords) |
|
||||
|
||||
- **Search returns real nurses.** `GET /api/v1/search/nurses?service_category_id=1&city_id=101` (Elderly Care in
|
||||
Tehran) returns the two verified nurses' priced variants. Categories offered only by the unverified nurse
|
||||
(Infant Care, `service_category_id=3`) return an empty page — the `is_searchable` invariant holds.
|
||||
- **Trust badges are real.** `GET /api/v1/nurses/{id}/trust_badge` returns `isVerified=true` + credential types
|
||||
for the verified nurses, `false` for the unverified one.
|
||||
- **The variant builder's required-option step renders on the real path.** The demo seeder adds one
|
||||
cross-category **required** option group — شیفت / *Shift Type* (`Daytime`/`Night`/`Live-in`) — so
|
||||
`GET /api/v1/catalog/...` option-group reads are non-empty in Development (the "categories but no option
|
||||
groups" data gap). This group is **Development-only demo data**, not a production catalog decision.
|
||||
|
||||
## What did NOT change (important)
|
||||
- **No `USE_*_MOCK` flag was flipped** — de-mocking the client is still [Phase 4](../../../post-phase/refinement/refinement-phase-4-frontend-de-mock.md).
|
||||
This phase only makes the *backend* real path return data.
|
||||
- **No new endpoints, no contract changes, no new migration.** The 17 migrations are unchanged and current;
|
||||
the reference `HasData` seeds are untouched. The demo seeder writes through the existing entities/handlers.
|
||||
- **Nothing runs in Production/Staging.** The seeder is gated on `IsDevelopment()`.
|
||||
|
||||
## How to reset / re-seed
|
||||
- `docker compose down -v` (wipe the DB volume) then `dotnet run` → migrate + reference seed + demo seed from
|
||||
scratch. Re-running `dotnet run` against an already-seeded DB is a **no-op** (guarded on each persona's phone).
|
||||
|
||||
## Gotchas
|
||||
- **Money is IRR Rials** (e.g. per-24h live-in = `3500000`); on the wire variant prices are digit strings.
|
||||
- The unverified nurse **has** a variant and a covered area but `is_verified=0`, so the search maintainer
|
||||
computes `is_searchable=0`. Do not treat "has a variant" as "is discoverable".
|
||||
@@ -0,0 +1,92 @@
|
||||
# Refinement Phase 1 — Database: local-dev story, demo seed & migration hygiene — Report (2026-07-13)
|
||||
|
||||
## What was built
|
||||
Turned a freshly-migrated database from an empty shell (reference/lookup rows + one admin + one gateway) into
|
||||
a **populated demo marketplace**, so every real-path discovery/search/booking screen has data. **No new
|
||||
migration, no new endpoint, no contract change, and the reference `HasData` seeds are untouched** — the demo
|
||||
seeder writes *alongside* them through the existing entities and the real search maintainer.
|
||||
|
||||
- **Development-gated demo seeder (§3.2).** New `Persistence/Services/Seeding/DemoWorldSeeder.cs` (scoped,
|
||||
registered in `AddPersistenceServices`) + `DemoWorldDefinitions.cs` (the static, deterministic persona
|
||||
data). Invoked from `Program.cs` **only under `app.Environment.IsDevelopment()`**, after
|
||||
`SeedDefaultUsersAsync` + `SeedPaymentGatewaysAsync`, via the new `SeedDemoWorldAsync()` extension. It
|
||||
creates:
|
||||
- **3 nurses** (phone users + `nurse` role via `IAppUserManager`, each with a `nurse_profiles` row):
|
||||
two **fully verified** (`is_verified` flipped through the guarded `MarkVerified()`, a `nurse_verifications`
|
||||
row `approved`, credentials, a primary bank account with `matched_national_id=1`), one **unverified**
|
||||
(`pending`, no credentials/bank). Each verified nurse gets **2–3 `nurse_service_variants`** across price
|
||||
units (IRR `BIGINT`) + **`nurse_service_areas`** in Tehran (one whole-city `district_id=NULL`, some
|
||||
specific districts).
|
||||
- **2 customers** (phone users + `customer` role) each with a `customer_profiles` row, **1–2 `patients`**
|
||||
(with gender for same-gender matching + encrypted `initial_medical_notes`), and a **`customer_addresses`**
|
||||
row with coordinates.
|
||||
- **One cross-category, required option group** — شیفت / *Shift Type* (`Daytime`/`Night`/`Live-in`) — so the
|
||||
variant builder's required-option step renders on the real path in Development (the "categories but no
|
||||
option groups" data gap). Each variant answers it; the `OptionSetHash` is computed the same way the real
|
||||
`CreateVariant` handler does.
|
||||
- **The search index is driven through the real `ISearchIndexMaintainer.RebuildAsync`** (never hand-inserted)
|
||||
— the single place `is_searchable` is computed, so the seeded world is identical to one built by real usage.
|
||||
- **Idempotency (§5).** Every persona is guarded on its **phone number** (`GetUserByPhoneNumber`); the option
|
||||
group on its name. Re-running the seeder on an already-seeded DB is a no-op (it still re-derives the search
|
||||
index, which is itself idempotent).
|
||||
- **Local DB story finished (§3.1).** Phase 0 already delivered `docker-compose.yml` (SQL Server 2022 on
|
||||
1433), placeholder `appsettings*.json` connection strings, the `UserSecretsId`, and boot-time
|
||||
`MigrateAsync`. This phase **verified** those and **documented the explicit `dotnet ef database update` path**
|
||||
(for the migrations-off-boot / deploy direction Phase 7 formalises) plus the demo-seed reset flow in the
|
||||
RUNBOOK.
|
||||
- **Migration hygiene (§3.3).** Confirmed the 17 migrations are current and there are no pending model changes
|
||||
(the demo seeder adds runtime data, not schema — it changed no entity config and no migration). The three
|
||||
promised-but-missing forward-dep FKs and the boot-migrate multi-instance race are **explicitly out of scope**
|
||||
(Phase 6 / Phase 7 respectively), as the phase directs.
|
||||
|
||||
## The exact demo world (for the frontend de-mock phase to log in as)
|
||||
| Phone | Role | Who | Gender | State |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `09120000001` | nurse | زهرا عزیزی | female | **verified**, accepting · variants: Elderly per-24h `3500000`, Elderly per-hour `250000`, Post-Surgery per-day `2000000` · areas: whole-city Tehran + منطقه ۱ + منطقه ۳ |
|
||||
| `09120000002` | nurse | علی کریمی | male | **verified**, accepting · variants: Chronic per-day `1800000`, Post-Surgery per-24h `3200000` · areas: منطقه ۳/۶/۱۲ |
|
||||
| `09120000003` | nurse | مریم احمدی | female | **unverified** (pending) · Infant per-session `800000` · منطقه ۲ — never in search |
|
||||
| `09120000010` | customer | سارا محمدی | female | patients: حسن (male), فاطمه (female) · address in منطقه ۳ (coords) |
|
||||
| `09120000011` | customer | رضا حسینی | male | patient: امیرعلی (male, infant) · address in منطقه ۶ (coords) |
|
||||
|
||||
Tehran `city_id = 101`; districts are `1000+n`. Category ids: Elderly `1`, Post-Surgery `2`, Infant `3`,
|
||||
Chronic `4`.
|
||||
|
||||
## What is now testable (and exactly how)
|
||||
- **Automated (in the suite, +3 tests → 372 total):** `Baya.Test.Api/DemoWorldSeederTests` runs the seeder
|
||||
through real DI over the SQLite harness and asserts, over the real HTTP pipeline:
|
||||
1. `GET /api/v1/search/nurses?service_category_id=1&city_id=101` (Elderly, verified nurses) → `total > 0`;
|
||||
`service_category_id=3` (Infant, only the unverified nurse) → `total == 0`.
|
||||
2. The verified nurse's `trust_badge` → `isVerified=true` with non-empty `credentialTypes`; the unverified
|
||||
nurse's → `false`.
|
||||
3. Running the seeder **twice** leaves exactly `Nurses.Length` / `Customers.Length` rows (idempotent).
|
||||
- **Manual (the §7 proof, requires a reachable SQL Server):** wipe + `dotnet run` (Development) → the console
|
||||
logs `Demo world seeded: 3 nurse(s), 2 customer(s)…`; hit the two search routes above and the trust-badge
|
||||
routes in Swagger; re-run → `Demo world already seeded — no-op`. *(Not runnable in this environment: the SQL
|
||||
Server on `localhost:1433` here rejects the dev `sa` credential, so the automated HTTP-level tests above are
|
||||
the reproducible proof.)*
|
||||
|
||||
## What is mocked / waiting on a real service
|
||||
- **None introduced.** The seeder goes through the real handlers/maintainer where an invariant is at stake
|
||||
(verification flip, search reindex, `OptionSetHash`, encrypted-PII converters, `iban_hash`), so no seam and
|
||||
no `mocks-registry.md` entry. The bank account's `matched_national_id` is set directly to model a completed
|
||||
استعلام شبا inquiry (the mock `IBankAccountOwnershipVerifier` isn't invoked from the seeder).
|
||||
|
||||
## Contracts
|
||||
- **None produced / changed.** No new route or shape; `swagger.v1.json` not regenerated.
|
||||
|
||||
## Docs updated
|
||||
- `server/CLAUDE.md` — Persistence section notes the Development demo seeder and what it creates; Startup
|
||||
wiring notes the Development-only `SeedDemoWorldAsync()`.
|
||||
- `dev/post-phase/refinement/RUNBOOK.md` — added the demo-seed description, the demo-account table, the explicit
|
||||
`dotnet ef database update` path, and the reset/re-seed flow.
|
||||
- Handoff: `backend/handoff/after-refinement-phase-1.md`.
|
||||
|
||||
## Follow-ups for later phases
|
||||
- **Phase 4** — flip the client `USE_*_MOCK` flags; log in as the demo accounts above and de-mock home/search/
|
||||
booking against this real data.
|
||||
- **Phase 6** — the additive forward-dep FKs (`refunds.ticket_id`, `nurse_clawbacks.*_payout_id`,
|
||||
`invoices.partner_center_id`) + money-correctness; *not* in this phase.
|
||||
- **Phase 7** — split boot-time `MigrateAsync` out for the multi-instance / least-privilege deploy path.
|
||||
- **Optional later:** a couple of confirmed bookings + sessions for the demo pairs (ids 5001–5005) so the
|
||||
post-payment screens demo without running the funnel — deliberately deferred (the cleaner demo is to create
|
||||
bookings by running the real funnel once Phase 4 lands).
|
||||
Reference in New Issue
Block a user