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