clean and refine product docs structure
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Domain 1 — Identity & Access — Balinyaar docs</title>
|
||||
<link rel="stylesheet" href="../assets/doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<a class="brand" href="../index.html"><span class="dot"></span> Balinyaar docs</a>
|
||||
<p class="tagline">Trust-first home-nursing marketplace · Iran</p>
|
||||
<nav><div class="group"><div class="label">Start here</div><ul><li><a href="../index.html">Docs home</a></li><li><a href="../overview/platform-summary.html">Platform summary & ground truths</a></li></ul></div><div class="group"><div class="label">Business requirements</div><ul><li><a href="../business/index.html">Overview & MVP scope</a></li><li><a href="../business/01-actors-and-onboarding.html">1. Actors & onboarding</a></li><li><a href="../business/02-nurse-verification.html">2. Nurse verification</a></li><li><a href="../business/03-service-catalog-and-pricing.html">3. Service catalog & pricing</a></li><li><a href="../business/04-search-and-matching.html">4. Search & matching</a></li><li><a href="../business/05-booking-and-scheduling.html">5. Booking & scheduling</a></li><li><a href="../business/06-evv-and-service-delivery.html">6. EVV / service delivery</a></li><li><a href="../business/07-cancellation-and-refunds.html">7. Cancellation & refunds</a></li><li><a href="../business/08-payments-and-escrow.html">8. Payments & escrow</a></li><li><a href="../business/09-installments-bnpl.html">9. Installments / BNPL</a></li><li><a href="../business/10-payouts.html">10. Payouts to nurses</a></li><li><a href="../business/11-reviews-trust-and-safety.html">11. Reviews, trust & safety</a></li><li><a href="../business/12-messaging-and-emergencies.html">12. Messaging & emergencies</a></li><li><a href="../business/13-tax-invoicing-and-legal.html">13. Tax, invoicing & legal</a></li><li><a href="../business/14-notifications-and-admin.html">14. Notifications & admin</a></li></ul></div><div class="group"><div class="label">Database model</div><ul><li><a href="index.html">Overview & decisions</a></li><li><a href="diagrams.html">Diagrams</a></li><li><a class="active" href="01-identity-and-access.html">1. Identity & access</a></li><li><a href="02-geography.html">2. Geography</a></li><li><a href="03-services-and-pricing.html">3. Services & pricing</a></li><li><a href="04-verification-and-credentials.html">4. Verification & credentials</a></li><li><a href="05-booking-and-scheduling.html">5. Booking & scheduling</a></li><li><a href="06-payments-ledger-and-refunds.html">6. Payments, ledger & refunds</a></li><li><a href="07-payouts.html">7. Payouts</a></li><li><a href="08-bnpl.html">8. BNPL / installments</a></li><li><a href="09-messaging.html">9. Messaging</a></li><li><a href="10-reviews-and-records.html">10. Reviews & records</a></li><li><a href="11-notifications.html">11. Notifications</a></li><li><a href="12-audit-config-and-reference.html">12. Audit, config & reference</a></li><li><a href="13-partner-centers-and-future.html">13. Partner centers & future</a></li></ul></div><div class="group"><div class="label">Payments deep-dive</div><ul><li><a href="../payments/index.html">Overview & exec summary</a></li><li><a href="../payments/iranian-payment-reality.html">Iranian payment reality</a></li><li><a href="../payments/escrow-ledger.html">Escrow as a ledger</a></li><li><a href="../payments/bnpl-landscape.html">BNPL landscape & finding</a></li><li><a href="../payments/cancellation-and-payout.html">Cancellation & nurse payout</a></li><li><a href="../payments/integration-notes.html">Integration & schema touchpoints</a></li><li><a href="../payments/sources.html">Recommendations & sources</a></li></ul></div><div class="group"><div class="label">Research & strategy</div><ul><li><a href="../research/index.html">Overview & exec summary</a></li><li><a href="../research/market-and-competitors.html">Market & competitors</a></li><li><a href="../research/problems-and-risks.html">Problems & risks</a></li><li><a href="../research/verification.html">Verification (research)</a></li><li><a href="../research/legal-landscape.html">Legal landscape</a></li><li><a href="../research/go-to-market.html">Go-to-market & sources</a></li></ul></div><div class="group"><div class="label">Notes & more</div><ul><li><a href="../notes/open-questions.html">Open questions</a></li><li><a href="../notes/future-ideas.html">Future ideas</a></li><li><a href="../wireframes/index.html">Wireframes</a></li><li><a href="../fa/index.html">Farsi documents</a></li></ul></div></nav>
|
||||
</aside>
|
||||
<main class="main"><div class="content">
|
||||
<div class="topbar"><button class="theme-toggle" type="button" onclick="__t()">theme</button></div>
|
||||
<h1 id="domain-1-identity-access">Domain 1 — Identity & Access</h1>
|
||||
<p><a href="index.html">← Database Model</a></p>
|
||||
<p><strong>Related:</strong> business requirements — <a href="../business/01-actors-and-onboarding.html">Actors & onboarding</a>.</p>
|
||||
<h3 id="users-core"><code>users</code> [CORE] <a class="anchor" href="#users-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> The single identity record for every human actor — nurse, customer, admin. <code>role</code> decides which profile sub-table is populated. Phone is the primary login credential (OTP); national ID is filled only after KYC.</p>
|
||||
<p><strong>Why:</strong> One identity table avoids three near-duplicate user tables and lets auth, audit, and notifications treat everyone uniformly. Phone-as-primary matches Iranian OTP norms and is the key Shahkar matches against. National ID stays NULL until verified so an unverified registration can't masquerade as KYC-complete.</p>
|
||||
<p>Fields unchanged from baseline: <code>id</code>, <code>email</code> (enc, nullable), <code>phone</code> (enc, unique), <code>national_id</code> (enc, nullable), <code>national_id_verified_at</code>, <code>first_name</code>, <code>last_name</code>, <code>gender</code> <em>(promoted here — see note)</em>, <code>avatar_url</code>, <code>role</code> (<code>nurse</code>/<code>customer</code>/<code>admin</code>), <code>is_active</code>, <code>email_verified_at</code>, <code>phone_verified_at</code>, <code>last_login_at</code>, <code>last_login_ip</code>, <code>preferred_language</code>, <code>created_at</code>, <code>updated_at</code>, <code>deleted_at</code>.</p>
|
||||
<div class="table-wrap"><table><thead><tr><th>Field</th><th>Type</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td><code>gender</code></td><td>NVARCHAR(10) NULL</td><td><strong>NEW/clarified</strong> — <code>male</code>/<code>female</code>. Needed because <strong>same-gender caregiving is a near-hard requirement</strong> in Iranian bodily-care; nurse gender (from here) is matched against <code>booking_requests.required_caregiver_gender</code>.</td></tr>
|
||||
<tr><td><code>shahkar_verified_at</code></td><td>DATETIME2 NULL</td><td><strong>NEW</strong> — when the phone↔national-id binding was confirmed via Shahkar. Re-set to NULL (re-verify) on phone change.</td></tr>
|
||||
</tbody></table></div>
|
||||
<p><strong>Relations:</strong> 1:1 → <code>nurse_profiles</code> / <code>customer_profiles</code> (by role); 1:N → <code>user_sessions</code>, <code>user_roles</code>, <code>notifications</code>, <code>ticket_participants</code>. Admin users are referenced across the schema as <code>*_by_admin_id</code>.</p>
|
||||
<h3 id="user_sessions-core"><code>user_sessions</code> [CORE] <a class="anchor" href="#user_sessions-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Refresh-token session records. <strong>Why:</strong> Enables logout-everywhere and stolen-token revocation without a heavyweight session store. Unchanged: <code>id</code>, <code>user_id</code>, <code>refresh_token_hash</code>, <code>device_info</code>, <code>ip_address</code>, <code>is_revoked</code>, <code>revoked_at</code>, <code>expires_at</code>, <code>created_at</code>. <strong>Relations:</strong> N:1 → <code>users</code>.</p>
|
||||
<h3 id="roles-user_roles-core"><code>roles</code> / <code>user_roles</code> [CORE] <a class="anchor" href="#roles-user_roles-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> RBAC for admin staff only (nurses/customers use <code>users.role</code>). <strong>Why:</strong> A small admin team still needs separable finance/support/moderation permissions and a revocation history. <code>user_roles</code> keeps <code>granted_by</code>/<code>granted_at</code>/<code>revoked_at</code> for an audit trail. <strong>Relations:</strong> <code>users</code> N:N <code>roles</code> via <code>user_roles</code>.</p>
|
||||
<h3 id="nurse_profiles-core"><code>nurse_profiles</code> [CORE] <a class="anchor" href="#nurse_profiles-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Extended data for nurses, plus denormalized search/quality aggregates. <strong>Why separated from <code>users</code>:</strong> keeps the base identity table lean and isolates the (large) nurse-only attributes and the aggregates that search reads on every query.</p>
|
||||
<div class="table-wrap"><table><thead><tr><th>Field</th><th>Type</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td><code>id</code></td><td>BIGINT PK</td><td></td></tr>
|
||||
<tr><td><code>user_id</code></td><td>BIGINT FK → users UNIQUE</td><td>1:1</td></tr>
|
||||
<tr><td><code>partner_center_id</code></td><td>BIGINT FK → partner_centers NULL</td><td><strong>NEW</strong> — the licensed center that legally sponsors this nurse at launch (Asanism model). NULL once Balinyaar holds its own permit.</td></tr>
|
||||
<tr><td><code>bio</code>, <code>years_of_experience</code>, <code>education_level</code>, <code>education_field</code>, <code>specializations_json</code></td><td>…</td><td>Unchanged.</td></tr>
|
||||
<tr><td><code>is_verified</code></td><td>BIT NOT NULL DEFAULT 0</td><td><strong>Guarded</strong> — set <strong>only</strong> inside the transaction that confirms all required <code>verification_steps.status='passed'</code>. No direct write API (Principle 12).</td></tr>
|
||||
<tr><td><del><code>verification_status</code></del></td><td>—</td><td><strong>CUT</strong> — duplicated <code>nurse_verifications.status</code>; two copies drifted. <code>nurse_verifications.status</code> is now the single source of truth.</td></tr>
|
||||
<tr><td><code>is_accepting_bookings</code></td><td>BIT NOT NULL DEFAULT 0</td><td>Nurse can pause without losing verified status.</td></tr>
|
||||
<tr><td><code>average_rating</code>, <code>total_reviews</code>, <code>total_completed_bookings</code></td><td>…</td><td>Denormalized. <strong>Recompute rule now documented</strong>: updated on <strong>every</strong> review status transition (publish → +, hide/reject/unpublish → −) and on booking completion/dispute-reversal, plus a nightly reconciliation job. Fixes the "hide a 1★ review → rating stays inflated" drift.</td></tr>
|
||||
<tr><td><del><code>response_rate</code>, <code>avg_response_time_hours</code>, <code>profile_completion_score</code></del></td><td>—</td><td><strong>CUT for MVP</strong> — analytics columns on no money/safety path, each needing a maintenance job. Compute offline later.</td></tr>
|
||||
<tr><td><code>created_at</code>, <code>updated_at</code>, <code>deleted_at</code></td><td>…</td><td></td></tr>
|
||||
</tbody></table></div>
|
||||
<p><strong>Relations:</strong> 1:1 → <code>users</code>, <code>nurse_verifications</code>; 1:N → <code>nurse_service_variants</code>, <code>nurse_service_areas</code>, <code>nurse_bank_accounts</code>, <code>nurse_credentials</code>, <code>bookings</code>, <code>nurse_payouts</code>, <code>nurse_clawbacks</code>; N:1 → <code>partner_centers</code>.</p>
|
||||
<h3 id="customer_profiles-core"><code>customer_profiles</code> [CORE] <a class="anchor" href="#customer_profiles-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Lightweight extension for customers. <strong>Why intentionally thin:</strong> most customer reality lives in their <code>patients</code>, <code>customer_addresses</code>, and <code>bookings</code>. KYC for customers is deferred. Unchanged: <code>id</code>, <code>user_id</code> (unique), <code>default_emergency_contact_name</code>/<code>_phone</code> (enc), <code>created_at</code>, <code>updated_at</code>. <strong>CUT for MVP:</strong> <code>national_id_verified_at</code> (anti-fraud customer KYC — add when actually built). <strong>Relations:</strong> 1:1 → <code>users</code>; 1:N → <code>patients</code>, <code>customer_addresses</code>, <code>booking_requests</code>, <code>bookings</code>.</p>
|
||||
<h3 id="patients-core"><code>patients</code> [CORE] <a class="anchor" href="#patients-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> The person receiving care, <strong>separate from the payer</strong>. <strong>Why:</strong> the payer (adult child, spouse) is usually not the patient (elderly parent, newborn, post-surgical adult); one customer registers many patients, each with its own clinical baseline and longitudinal record. Unchanged: <code>id</code>, <code>customer_id</code>, <code>display_name</code>, <code>first_name</code>, <code>last_name</code>, <code>birth_date</code>, <code>gender</code>, <code>blood_type</code>, <code>initial_medical_notes</code> (enc), <code>is_active</code>, timestamps. <strong>Relations:</strong> N:1 → <code>customer_profiles</code>; 1:N → <code>booking_requests</code>, <code>patient_care_records</code>. <strong>Tenancy invariant:</strong> a <code>booking_request.patient_id</code> must belong to the same <code>customer_id</code>.</p>
|
||||
<h3 id="customer_addresses-core"><code>customer_addresses</code> [CORE] <a class="anchor" href="#customer_addresses-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Saved service locations; the encrypted address + coordinates for EVV distance checks. <strong>Why coordinates:</strong> EVV check-in compares the nurse's GPS against the booking address within tolerance. Unchanged fields, plus: <strong>filtered <code>UNIQUE(customer_id) WHERE is_primary=1</code></strong> so exactly one primary exists (prevents ambiguous default). <strong>Relations:</strong> N:1 → <code>customer_profiles</code>, <code>cities</code>, <code>districts</code>; referenced by <code>booking_requests</code>/<code>bookings</code>.</p>
|
||||
<h3 id="nurse_bank_accounts-core"><code>nurse_bank_accounts</code> [CORE] <a class="anchor" href="#nurse_bank_accounts-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Payout destination (IBAN/Sheba). <strong>Why hardened:</strong> the IBAN is the single place real money leaves the platform — the original "admin eyeballs the IBAN" check is exactly the forgeable, money-mule-risk link the research warns about.</p>
|
||||
<div class="table-wrap"><table><thead><tr><th>Field</th><th>Type</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td><code>id</code>, <code>nurse_id</code>, <code>bank_name</code>, <code>account_holder_name</code> (enc), <code>iban</code> (enc), <code>is_primary</code>, <code>is_verified</code>, <code>verified_by_admin_id</code>, <code>verified_at</code>, timestamps</td><td>…</td><td>Baseline.</td></tr>
|
||||
<tr><td><code>iban_hash</code></td><td>NVARCHAR(64)</td><td><strong>NEW</strong> — deterministic hash for a <strong>UNIQUE</strong> constraint (same IBAN must not silently serve two nurses).</td></tr>
|
||||
<tr><td><code>matched_national_id</code></td><td>BIT NULL</td><td><strong>NEW</strong> — result of an automated <strong>IBAN-owner ↔ national-id inquiry (استعلام شبا)</strong> via a KYC vendor. First payout is gated on a match, not on admin eyeballing.</td></tr>
|
||||
<tr><td><code>account_holder_from_bank</code></td><td>NVARCHAR(200) NULL</td><td><strong>NEW</strong> — name returned by the bank inquiry, snapshot.</td></tr>
|
||||
<tr><td><code>ownership_vendor_ref</code></td><td>NVARCHAR(200) NULL</td><td><strong>NEW</strong> — vendor transaction id for audit.</td></tr>
|
||||
</tbody></table></div>
|
||||
<p>Constraints: <strong>filtered <code>UNIQUE(nurse_id) WHERE is_primary=1</code></strong>; <code>UNIQUE(iban_hash)</code>. <strong>Relations:</strong> N:1 → <code>nurse_profiles</code>; 1:N → <code>nurse_payouts</code>.</p>
|
||||
<a class="back-to-top" href="#">↑ Back to top</a>
|
||||
</div></main>
|
||||
</div>
|
||||
<script>
|
||||
(function(){var k='balinyaar-docs-theme';var s=localStorage.getItem(k);
|
||||
if(s)document.documentElement.setAttribute('data-theme',s);
|
||||
else if(matchMedia('(prefers-color-scheme: dark)').matches)document.documentElement.setAttribute('data-theme','dark');})();
|
||||
function __t(){var d=document.documentElement;var n=d.getAttribute('data-theme')==='dark'?'light':'dark';
|
||||
d.setAttribute('data-theme',n);localStorage.setItem('balinyaar-docs-theme',n);}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user