7.3 KiB
7.3 KiB
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 inAddPersistenceServices) +DemoWorldDefinitions.cs(the static, deterministic persona data). Invoked fromProgram.csonly underapp.Environment.IsDevelopment(), afterSeedDefaultUsersAsync+SeedPaymentGatewaysAsync, via the newSeedDemoWorldAsync()extension. It creates:- 3 nurses (phone users +
nurserole viaIAppUserManager, each with anurse_profilesrow): two fully verified (is_verifiedflipped through the guardedMarkVerified(), anurse_verificationsrowapproved, credentials, a primary bank account withmatched_national_id=1), one unverified (pending, no credentials/bank). Each verified nurse gets 2–3nurse_service_variantsacross price units (IRRBIGINT) +nurse_service_areasin Tehran (one whole-citydistrict_id=NULL, some specific districts). - 2 customers (phone users +
customerrole) each with acustomer_profilesrow, 1–2patients(with gender for same-gender matching + encryptedinitial_medical_notes), and acustomer_addressesrow 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; theOptionSetHashis computed the same way the realCreateVarianthandler does. - The search index is driven through the real
ISearchIndexMaintainer.RebuildAsync(never hand-inserted) — the single placeis_searchableis computed, so the seeded world is identical to one built by real usage.
- 3 nurses (phone users +
- 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), placeholderappsettings*.jsonconnection strings, theUserSecretsId, and boot-timeMigrateAsync. This phase verified those and documented the explicitdotnet ef database updatepath (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/DemoWorldSeederTestsruns the seeder through real DI over the SQLite harness and asserts, over the real HTTP pipeline: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.- The verified nurse's
trust_badge→isVerified=truewith non-emptycredentialTypes; the unverified nurse's →false. - Running the seeder twice leaves exactly
Nurses.Length/Customers.Lengthrows (idempotent).
- Manual (the §7 proof, requires a reachable SQL Server): wipe +
dotnet run(Development) → the console logsDemo 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 onlocalhost:1433here rejects the devsacredential, 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 nomocks-registry.mdentry. The bank account'smatched_national_idis set directly to model a completed استعلام شبا inquiry (the mockIBankAccountOwnershipVerifierisn't invoked from the seeder).
Contracts
- None produced / changed. No new route or shape;
swagger.v1.jsonnot regenerated.
Docs updated
server/CLAUDE.md— Persistence section notes the Development demo seeder and what it creates; Startup wiring notes the Development-onlySeedDemoWorldAsync().dev/post-phase/refinement/RUNBOOK.md— added the demo-seed description, the demo-account table, the explicitdotnet ef database updatepath, 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_*_MOCKflags; 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
MigrateAsyncout 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).