# 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).