# Refinement Phase 1 — Database: local-dev story, demo seed & migration hygiene > **Mission:** turn a fresh database into a *populated marketplace* instead of an empty shell, and give the > project a clean local-development database story. The 17 EF migrations are current and apply cleanly, but a > fresh DB seeds only lookup tables + one admin + one gateway — **no nurses, no variants, no search rows, no > bookings** — so every discovery/search/booking screen is empty on the real path. This phase adds a > Development-gated demo seed and a repeatable local DB setup. > > **Track:** backend (+ DB) · **Depends on:** [Phase 0](refinement-phase-0-bring-up.md) · **Unlocks:** real > search/discovery/booking data for [Phase 4](refinement-phase-4-frontend-de-mock.md) > **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).** ## 1. Context — where this sits **Myth to correct up front:** the migrations are **not "behind."** There are 17 migrations (`20260628…InitialBaseline` → `20260709…MessagingAndPartnerCenters`) tracking every backend phase b0–b15; the model snapshot is in sync and the working tree is clean. If a developer *thinks* migrations are behind, it's because **the target database was never migrated** (they were running the frontend entirely on mocks) — not because migration files are missing. `Program.cs` calls `MigrateAsync()` on every non-Testing boot, so simply booting against a fresh DB brings it fully up to date. The real database problems are two: 1. **No local-dev database story.** Both appsettings point at the committed remote SQL Server; there is no localhost default and no SQLite fallback for `dotnet run`. (Phase 0 introduced the local instance; this phase makes it the clean, documented default and adds demo data.) 2. **No transactional/domain seed data.** The only seed is reference/lookup tables (31 provinces + cities + Tehran's 22 districts, the 5-category catalog skeleton, verification step types, review tags, holidays, cancellation policies, platform config, the invoice-number counter) plus one `admin`/`qw123321` user and one sandbox ZarinPal gateway. There are **no nurses, customers, profiles, variants, service areas, or `nurse_search_index` rows**, so `GET /api/v1/search/nurses` returns an empty list even after Phase 3/4 make the frontend call it for real. **What already exists (do not rebuild):** all 17 migrations; the `HasData` reference seeds; the boot-time `MigrateAsync` + `SeedDefaultUsersAsync` + `SeedPaymentGatewaysAsync`. This phase adds an *additional*, environment-gated demo seeder — it does not touch the reference seeds or the migration set. ## 2. Required reading (do this first) - `server/src/API/Baya.Web.Api/Program.cs:99-104` — the boot migrate + seed calls. - `server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs` — `ApplyMigrationsAsync`, `SeedPaymentGatewaysAsync`, and where a new demo seeder would register. - `server/src/Infrastructure/Baya.Infrastructure.Identity/Identity/SeedDatabaseService/SeedDataBase.cs` — the existing role + admin seed pattern to mirror. - The catalog seed (`Persistence/Configuration/CatalogConfig/`) and the frontend-backend-gaps note that a fresh catalog has **categories but no option groups** — the variant builder's required-option step has nothing to render until an admin authors groups. Decide: seed a representative option-group set, or accept the empty state until the f15 admin catalog UI is used. - `product/data-model/` for the entities you'll seed (nurse_profiles, nurse_service_variants, nurse_service_areas, nurse_search_index, patients, customer_addresses) so the demo world is *valid* per the business rules (verified nurse → `is_searchable` requires `is_verified=1 AND accepting AND active variant`). - The b7 search invariant in `server/CLAUDE.md` ("Search & matching") — a demo nurse only appears in search if the maintainer wrote a `nurse_search_index` row with `is_searchable=1`. ## 3. Scope — build this ### 3.1 A clean local database default (finish what Phase 0 started) - Ensure `server/docker-compose.yml` (or a documented LocalDB path) gives a one-command local SQL Server. - The Development connection string resolves from user-secrets/env (not a committed file) to that local instance; `appsettings*.json` carry only a placeholder. - Verify `dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api` works against the local DB, and that booting also self-migrates. Document both paths (boot-migrate for dev convenience; explicit `ef database update` for the deploy/migrations-off-boot direction that [Phase 7](refinement-phase-7-unattended-ops.md) will formalise). ### 3.2 A Development-gated demo seeder (`SeedDemoWorldAsync`) A new seeder, invoked from `Program.cs` **only when `app.Environment.IsDevelopment()`** (never in Production/Staging), that idempotently creates a coherent demo marketplace. It must produce data that satisfies every downstream invariant so the real-path screens actually populate: - **2–3 nurse accounts** (phone-based users with the `nurse` role) each with a `nurse_profiles` row — at least one **fully verified** (`is_verified=1`, verification `approved`, a verified primary bank account with `matched_national_id=1`) and one **unverified** (so the trust badge / publish-gate states demo). - Each verified nurse: **2–4 `nurse_service_variants`** across price units (with valid option answers for the seeded categories) + **`nurse_service_areas`** in Tehran (some whole-city `district_id=NULL`, some specific districts) → and therefore **`nurse_search_index` rows with `is_searchable=1`** (drive this through the real `ISearchIndexMaintainer.RebuildAsync` / the reindex hooks, **not** by hand-inserting index rows, so the projection stays truthful). - **1–2 customer accounts** each with a `customer_profiles` row, **1–2 `patients`** (with relation/conditions once [Phase 3](refinement-phase-3-contract-batch.md) REQ-005 lands — until then, the fields the DTO supports), and **1–2 `customer_addresses`** with coordinates (so booking + EVV have a real door location). - **A representative service-option-group set** per category (or a decision to defer to the f15 admin UI) so the variant builder's required-option step renders on the real path (the frontend-backend-gaps "data gap"). - *(Optional, if you want the post-payment screens to demo without running the full funnel)* a couple of **confirmed bookings + sessions** for the demo customer/nurse pairs, mirroring the frontend's seeded booking ids (5001–5005) so deep-links line up. Keep this optional — the cleaner demo is to create bookings by actually running the funnel once the frontend is real. Idempotency: guard every insert on a stable natural key (phone number, variant option-set hash) so re-running the seeder on an already-seeded DB is a no-op — the same discipline the existing `SeedPaymentGatewaysAsync` uses. ### 3.3 Migration hygiene notes (document, mostly) - Confirm no pending model changes (`dotnet ef migrations has-pending-model-changes` or equivalent) — the tree is clean today; keep it that way. - Note (do **not** fix here — it's [Phase 7](refinement-phase-7-unattended-ops.md)) that boot-time `MigrateAsync` races under multi-instance start-up and needs the app login to hold DDL rights; the deploy-time migration split belongs there. - The three promised-but-missing forward-dep FKs (`refunds.ticket_id`, `nurse_clawbacks.*_payout_id`, `invoices.partner_center_id`) are **not** in this phase — they're an additive migration in [Phase 6](refinement-phase-6-money-correctness.md) alongside the money-correctness work. ## 4. Mocks & seams in this phase None introduced. The demo seeder must go **through the real handlers/maintainer** where an invariant is at stake (verification flip, search reindex) rather than raw-inserting, so the seeded world is identical to one built by real usage. ## 5. Critical rules you must not get wrong - **The demo seeder is Development-only.** Gate on `IsDevelopment()`. Seeding fake nurses/customers into a production DB would be a data-integrity and trust disaster. - **A nurse only appears in search if `is_searchable=1`** — which requires verified + accepting + an active variant + a `nurse_search_index` row. Seed the *causes*, let the maintainer compute the index; don't fake the index. - **Money stays IRR `BIGINT`; variant prices are digit-strings on the wire.** Seed valid amounts. - **Idempotent seeding.** Re-running must not duplicate nurses/variants/areas. - **Don't touch the reference `HasData` seeds or the migration files** — add alongside. ## 6. Definition of Done On top of the shared [definition-of-done.md](../../phases/_shared/definition-of-done.md): - [ ] A documented one-command local DB + a Development-gated `SeedDemoWorldAsync`; `dotnet build`/`dotnet test` green; the seeder is idempotent (run twice, same result). - [ ] Against a fresh local DB, after boot, `GET /api/v1/search/nurses` (via Swagger/curl) returns the seeded verified nurse(s) with variants — proven **without** any frontend change. - [ ] The verified demo nurse has a trust badge, variants, and coverage areas; the unverified one does not surface in search. - [ ] Migration state confirmed current (no pending model changes) and the local `ef database update` path documented. ## 7. How to test (what a human can verify after this phase) 1. Drop/recreate the local DB, `dotnet run` → observe migrate + reference seed + demo seed in the logs. 2. Swagger → `POST /api/v1/search/nurses` (or the documented search route) with a Tehran city filter → **expect the seeded verified nurse(s)** with priced variants in the result page. 3. `GET /api/v1/nurses/{id}/trust_badge` for the verified nurse → verified badge; for the unverified → not verified. 4. Re-run the app → the demo seeder logs "already seeded / no-op" and counts don't double. ## 8. Hand off & document (close the phase) - Update the runbook (Phase 0) with the local DB + demo-seed steps. - Note in `server/CLAUDE.md` that a Development demo seeder exists and what it creates. - Report: the exact demo world (ids, phones, which nurse is verified), so the frontend de-mock phase can log in as those accounts. Save a memory note that migrations are current and the real gap was seed data + local DB.