"""Identity DB schema: direct identifiers (152-FZ personal data). SPEC ยง5, ADR-003. Why a separate database: a leak or over-broad query on the clinical side must not reveal who the patient is. Names, SNILS, phones and Telegram chat ids live only here; the clinical DB stores health data keyed by `token` = HMAC(PSEUDO_KEY, SNILS) (`app.core.privacy.pseudo_id`). Without the key and this DB the clinical data is pseudonymised. In compose each DB has its own Postgres role with no access to the other one. Only registration and doctor-notification code may import this module (guardrail #5). """ from datetime import datetime from sqlalchemy import BigInteger, Boolean, ForeignKey, SmallInteger, String, Text from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from app.db.types import TZDateTime TOKEN_LEN = 32 class IdentityBase(DeclarativeBase): """Metadata of the identity DB only; never mixed with clinical metadata.""" class Patient(IdentityBase): __tablename__ = "patients" token: Mapped[str] = mapped_column(String(TOKEN_LEN), primary_key=True) display_name: Mapped[str] = mapped_column(Text) snils: Mapped[str] = mapped_column(String(14), unique=True) phone: Mapped[str | None] = mapped_column(String(32)) tg_chat_id: Mapped[int | None] = mapped_column(BigInteger, unique=True) birth_year: Mapped[int | None] = mapped_column(SmallInteger) sex: Mapped[str | None] = mapped_column(String(1)) # "m" | "f" clinic_id: Mapped[str] = mapped_column(String(64)) doctor_id: Mapped[str] = mapped_column(String(64), index=True) # NULL consent_at = no consent: processing of health data is forbidden (152-FZ art. 10). consent_at: Mapped[datetime | None] = mapped_column(TZDateTime) consent_ver: Mapped[str | None] = mapped_column(String(32)) revoked_at: Mapped[datetime | None] = mapped_column(TZDateTime) created_at: Mapped[datetime] = mapped_column(TZDateTime) class Doctor(IdentityBase): __tablename__ = "doctors" doctor_id: Mapped[str] = mapped_column(String(64), primary_key=True) full_name: Mapped[str] = mapped_column(Text) clinic_id: Mapped[str] = mapped_column(String(64)) notify_chat: Mapped[int | None] = mapped_column(BigInteger) on_call: Mapped[bool] = mapped_column(Boolean, default=False) class ConnectCode(IdentityBase): """One-time code the doctor gives the patient (QR) to bind the bot chat to the patient record.""" __tablename__ = "connect_codes" code: Mapped[str] = mapped_column(String(32), primary_key=True) token: Mapped[str] = mapped_column(ForeignKey("patients.token"), index=True) doctor_id: Mapped[str] = mapped_column(String(64)) created_at: Mapped[datetime] = mapped_column(TZDateTime) used_at: Mapped[datetime | None] = mapped_column(TZDateTime)