openapi: 3.0.0
paths:
  /api/auth/register/patient:
    post:
      description: Creates the account, signs it in, and sets the session cookie.
      operationId: AuthController_registerPatient
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterPatientDto'
      responses:
        '201':
          description: The new patient account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionUserDto'
      summary: Register a patient
      tags:
        - auth
      security: []
  /api/auth/register/doctor:
    post:
      description: >-
        Creates the account and signs it in. The profile starts awaiting
        administrator approval.
      operationId: AuthController_registerDoctor
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterDoctorDto'
      responses:
        '201':
          description: The new doctor account, with approval awaiting review.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionUserDto'
      summary: Register a doctor
      tags:
        - auth
      security: []
  /api/auth/login:
    post:
      description: Sets the session cookie and returns the signed-in account.
      operationId: AuthController_login
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginDto'
      responses:
        '200':
          description: The signed-in account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionUserDto'
      summary: Sign in
      tags:
        - auth
      security: []
  /api/auth/logout:
    post:
      description: >-
        Clears the session cookie. Succeeds whether or not a session was
        present.
      operationId: AuthController_logout
      parameters: []
      responses:
        '200':
          description: The cookie was cleared.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LogoutResponseDto'
      summary: Sign out
      tags:
        - auth
      security:
        - session: []
  /api/auth/me:
    get:
      description: Reads the account from the session cookie. Re-read on every request.
      operationId: AuthController_me
      parameters: []
      responses:
        '200':
          description: The signed-in account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionUserDto'
      summary: The signed-in account
      tags:
        - auth
      security:
        - session: []
  /api/patients/me/profile:
    get:
      description: Acts on the caller’s own profile only; the id is taken from the session.
      operationId: UsersController_profile
      parameters: []
      responses:
        '200':
          description: The caller’s profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PatientProfileDto'
      summary: Read the calling patient’s profile
      tags:
        - users
      security:
        - session: []
      x-roles:
        - PATIENT
    patch:
      operationId: UsersController_update
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePatientProfileDto'
      responses:
        '200':
          description: The profile as saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PatientProfileDto'
      summary: Update the calling patient’s profile
      tags:
        - users
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/patients/me/history:
    post:
      description: An allergy, a current medication, a chronic condition, or a free note.
      operationId: UsersController_addHistory
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddHistoryEntryDto'
      responses:
        '201':
          description: The entry as recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MedicalHistoryEntryDto'
      summary: Add a medical history entry
      tags:
        - users
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/patients/me/history/{id}:
    delete:
      operationId: UsersController_removeHistory
      parameters:
        - name: id
          required: true
          in: path
          description: The history entry to remove.
          schema:
            type: string
      responses:
        '204':
          description: The entry was removed.
      summary: Remove a medical history entry
      tags:
        - users
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/availability/windows:
    get:
      description: Every route here acts on the calling doctor's own schedule.
      operationId: AvailabilityController_windows
      parameters: []
      responses:
        '200':
          description: The doctor's windows.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AvailabilityWindowDto'
      summary: List the doctor's weekly windows
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
    post:
      operationId: AvailabilityController_addWindow
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWindowDto'
      responses:
        '201':
          description: The window as stored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AvailabilityWindowDto'
      summary: Add a weekly window
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/availability/windows/{id}:
    delete:
      operationId: AvailabilityController_removeWindow
      parameters:
        - name: id
          required: true
          in: path
          description: The window to remove.
          schema:
            type: string
      responses:
        '204':
          description: The window was removed.
      summary: Remove a weekly window
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/availability/exceptions:
    get:
      description: Dates that override the weekly windows entirely.
      operationId: AvailabilityController_exceptions
      parameters: []
      responses:
        '200':
          description: The doctor's exceptions.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AvailabilityExceptionDto'
      summary: List dated exceptions
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
    post:
      operationId: AvailabilityController_addException
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateExceptionDto'
      responses:
        '201':
          description: The exception as stored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AvailabilityExceptionDto'
      summary: Mark a date unavailable
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/availability/exceptions/{id}:
    delete:
      operationId: AvailabilityController_removeException
      parameters:
        - name: id
          required: true
          in: path
          description: The exception to remove.
          schema:
            type: string
      responses:
        '204':
          description: The exception was removed.
      summary: Remove a dated exception
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/availability/slots:
    get:
      description: Derived from windows and exceptions, for previewing the schedule.
      operationId: AvailabilityController_slots
      parameters: []
      responses:
        '200':
          description: Currently offerable slots.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SlotDto'
      summary: The doctor's own offerable slots
      tags:
        - availability
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/doctors/me:
    get:
      description: Includes the fields a public listing omits, such as the license number.
      operationId: DoctorsController_me
      parameters: []
      responses:
        '200':
          description: The calling doctor’s profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DoctorDetailDto'
      summary: The calling doctor’s own profile
      tags:
        - doctors
      security:
        - session: []
      x-roles:
        - DOCTOR
    patch:
      operationId: DoctorsController_updateMe
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDoctorProfileDto'
      responses:
        '200':
          description: The profile as saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DoctorDetailDto'
      summary: Update the calling doctor’s profile
      tags:
        - doctors
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/doctors:
    get:
      description: >-
        Returns approved doctors only. An unapproved doctor is visible to
        administrators and to themselves, never here.
      operationId: DoctorsController_list
      parameters:
        - name: q
          required: false
          in: query
          description: Free text matched against name and specialization.
          schema:
            maxLength: 120
            type: string
        - name: specialization
          required: false
          in: query
          schema:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        - name: availableSoon
          required: false
          in: query
          description: Restrict to doctors with a slot inside the "soon" horizon.
          schema:
            type: boolean
      responses:
        '200':
          description: Matching doctors.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DoctorSummaryDto'
      summary: Search the doctor directory
      tags:
        - doctors
      security:
        - session: []
  /api/doctors/{id}:
    get:
      operationId: DoctorsController_detail
      parameters:
        - name: id
          required: true
          in: path
          description: The doctor profile id, not the user id.
          schema:
            type: string
      responses:
        '200':
          description: The doctor, with their soonest slots.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DoctorDetailDto'
      summary: Read one doctor
      tags:
        - doctors
      security:
        - session: []
  /api/doctors/{id}/slots:
    get:
      description: >-
        Derived from the doctor’s windows and exceptions. A doctor whose
        approval is not yet granted returns 404 from the lookup behind this
        route.
      operationId: DoctorsController_slots
      parameters:
        - name: id
          required: true
          in: path
          description: The doctor profile id, not the user id.
          schema:
            type: string
      responses:
        '200':
          description: Currently offerable slots.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SlotDto'
      summary: Offerable slots for a doctor
      tags:
        - doctors
      security:
        - session: []
  /api/appointments:
    get:
      description: >-
        Scoped to the caller: a patient sees their own, a doctor the ones booked
        with them.
      operationId: AppointmentsController_list
      parameters:
        - name: scope
          required: false
          in: query
          description: Defaults to `all`.
          schema:
            enum:
              - upcoming
              - past
              - all
            type: string
      responses:
        '200':
          description: The caller’s appointments.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AppointmentDto'
      summary: List the calling account’s appointments
      tags:
        - appointments
      security:
        - session: []
    post:
      description: The requested slot must be one the doctor currently offers.
      operationId: AppointmentsController_book
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookAppointmentDto'
      responses:
        '201':
          description: The booked appointment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentDto'
      summary: Book an appointment
      tags:
        - appointments
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/appointments/{id}:
    get:
      description: Refused unless the caller is one of the two participants.
      operationId: AppointmentsController_get
      parameters:
        - name: id
          required: true
          in: path
          description: The appointment id.
          schema:
            type: string
      responses:
        '200':
          description: The appointment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentDto'
      summary: Read one appointment
      tags:
        - appointments
      security:
        - session: []
  /api/appointments/{id}/reschedule:
    post:
      description: Only the patient may move an appointment, and only before it starts.
      operationId: AppointmentsController_reschedule
      parameters:
        - name: id
          required: true
          in: path
          description: The appointment id.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RescheduleAppointmentDto'
      responses:
        '201':
          description: The appointment at its new time.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentDto'
      summary: Reschedule an appointment
      tags:
        - appointments
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/appointments/{id}/cancel:
    post:
      description: >-
        Either participant may cancel. Refused once the consultation is
        completed or already cancelled. There is no time restriction — an
        appointment whose start has passed can still be cancelled.
      operationId: AppointmentsController_cancel
      parameters:
        - name: id
          required: true
          in: path
          description: The appointment id.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelAppointmentDto'
      responses:
        '201':
          description: The cancelled appointment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentDto'
      summary: Cancel an appointment
      tags:
        - appointments
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/consultations/{appointmentId}:
    get:
      description: >-
        Every route here is scoped to an appointment the caller is a participant
        in. The clinical context is returned to the doctor only.
      operationId: ConsultationsController_context
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the consultation belongs to.
          schema:
            type: string
      responses:
        '200':
          description: The appointment, its session state, and the thread so far.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsultationContextDto'
      summary: The state of a consultation
      tags:
        - consultations
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/consultations/{appointmentId}/join:
    post:
      description: >-
        Records the caller’s arrival. Whether joining is allowed is decided by
        the appointment’s state, not by the clock: a participant who is early or
        late still belongs in this room. Refused once the consultation has
        completed or the appointment was cancelled.
      operationId: ConsultationsController_join
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the consultation belongs to.
          schema:
            type: string
      responses:
        '201':
          description: The consultation, updated with this caller’s arrival.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsultationContextDto'
      summary: Join the consultation
      tags:
        - consultations
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/consultations/{appointmentId}/complete:
    post:
      description: >-
        The doctor ends the session. A patient cannot close a consultation, and
        a doctor who did not take part cannot either.
      operationId: ConsultationsController_complete
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the consultation belongs to.
          schema:
            type: string
      responses:
        '201':
          description: The consultation, now completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsultationContextDto'
      summary: Complete the consultation
      tags:
        - consultations
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/consultations/{appointmentId}/messages:
    post:
      description: Both participants may write. Messages are retained with the record.
      operationId: ConsultationsController_send
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the consultation belongs to.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageDto'
      responses:
        '201':
          description: The stored message.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsultationMessageDto'
      summary: Post a message
      tags:
        - consultations
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/records/me:
    get:
      description: >-
        Every consultation the caller attended, with its note, prescriptions and
        summary.
      operationId: RecordsController_mine
      parameters: []
      responses:
        '200':
          description: The caller’s record entries.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MedicalRecordEntryDto'
      summary: The calling patient’s own record
      tags:
        - records
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/records/patients/{patientId}:
    get:
      description: >-
        Refused unless the calling doctor is a participant in one of that
        patient’s consultations.
      operationId: RecordsController_patient
      parameters:
        - name: patientId
          required: true
          in: path
          description: The patient profile id, not the user id.
          schema:
            type: string
      responses:
        '200':
          description: That patient’s record entries.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MedicalRecordEntryDto'
      summary: A patient’s record, as a doctor sees it
      tags:
        - records
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/records/appointments/{appointmentId}:
    get:
      description: >-
        Both participants may read it. The summary is withheld while it is
        stale.
      operationId: RecordsController_forAppointment
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the record entry belongs to.
          schema:
            type: string
      responses:
        '200':
          description: The note, prescriptions and summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MedicalRecordEntryDto'
      summary: The record entry for one appointment
      tags:
        - records
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/records/assist:
    get:
      description: >-
        Authenticated on purpose: the answer says something about this
        deployment’s configuration, so it is not public.
      operationId: RecordsController_assist
      parameters: []
      responses:
        '200':
          description: Availability, and the model that would answer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssistAvailabilityDto'
      summary: Whether clinical assist is available
      tags:
        - records
      security:
        - session: []
      x-roles:
        - PATIENT
        - DOCTOR
  /api/records/appointments/{appointmentId}/draft:
    get:
      description: Null when none has been generated, so reloading the form costs nothing.
      operationId: RecordsController_draft
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment the draft belongs to.
          schema:
            type: string
      responses:
        '200':
          description: The stored draft, or null.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/NoteDraftDto'
                  - type: 'null'
      summary: The draft already generated for an appointment
      tags:
        - records
      security:
        - session: []
      x-roles:
        - DOCTOR
    post:
      description: >-
        Generated scaffolding, not the record: it pre-fills the form and nothing
        else, and the doctor’s own save remains the only writer. A refusal — a
        transcript too thin to draft from, or assistance unavailable — is a 201
        carrying `status: "refused"`, not an error.
      operationId: RecordsController_generateDraft
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment to draft from.
          schema:
            type: string
      responses:
        '201':
          description: A draft, or the reason none was produced.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/DraftedNoteResultDto'
                  - $ref: '#/components/schemas/RefusedNoteResultDto'
      summary: Generate a note draft from the transcript
      tags:
        - records
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/records/appointments/{appointmentId}/note:
    put:
      description: >-
        Only a doctor who took part in the consultation may write the note.
        Saving again revises it: notes are never deleted, and an earlier version
        is superseded rather than removed.
      operationId: RecordsController_upsertNote
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment being written up.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertNoteDto'
      responses:
        '200':
          description: The note as saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsultationNoteDto'
      summary: Sign or revise the consultation note
      tags:
        - records
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/records/appointments/{appointmentId}/prescriptions:
    post:
      description: >-
        Only a doctor who took part in the consultation may prescribe.
        Prescriptions are retained, never deleted.
      operationId: RecordsController_addPrescription
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: The appointment being prescribed for.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePrescriptionDto'
      responses:
        '201':
          description: The prescription as issued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PrescriptionDto'
      summary: Add a prescription
      tags:
        - records
      security:
        - session: []
      x-roles:
        - DOCTOR
  /api/matching/symptoms:
    get:
      description: >-
        The options the intake form offers, including which ones are emergency
        indicators.
      operationId: MatchingController_catalogue
      parameters: []
      responses:
        '200':
          description: Every symptom the rules recognise.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SymptomOptionDto'
      summary: The symptom catalogue
      tags:
        - matching
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/matching:
    post:
      description: >-
        Deterministic: the same intake always produces the same result, so a
        patient who retries does not get a different answer. A rule that matches
        nothing falls back rather than returning an empty list.
      operationId: MatchingController_match
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchIntakeDto'
      responses:
        '200':
          description: Suggestions, with any emergency warning.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchResultDto'
      summary: Matching doctors for a described concern
      tags:
        - matching
      security:
        - session: []
      x-roles:
        - PATIENT
  /api/admin/users:
    get:
      description: 'Reads leave no audit entry: the log records changes, not lookups.'
      operationId: AdminController_users
      parameters:
        - name: q
          required: false
          in: query
          schema:
            maxLength: 120
            type: string
        - name: role
          required: false
          in: query
          schema:
            type: string
            enum:
              - PATIENT
              - DOCTOR
              - ADMIN
      responses:
        '200':
          description: Matching accounts.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminUserRowDto'
      summary: List accounts
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/users/{id}/activate:
    post:
      operationId: AdminController_activate
      parameters:
        - name: id
          required: true
          in: path
          description: The account to activate.
          schema:
            type: string
      responses:
        '201':
          description: The account, now active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminUserRowDto'
      summary: Activate an account
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/users/{id}/suspend:
    post:
      description: Takes effect on the account’s next request, not when its token expires.
      operationId: AdminController_suspend
      parameters:
        - name: id
          required: true
          in: path
          description: The account to suspend.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SuspendAccountDto'
      responses:
        '201':
          description: The account, now suspended.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminUserRowDto'
      summary: Suspend an account
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/users/{id}/deactivate:
    post:
      operationId: AdminController_deactivate
      parameters:
        - name: id
          required: true
          in: path
          description: The account to deactivate.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SuspendAccountDto'
      responses:
        '201':
          description: The account, now deactivated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminUserRowDto'
      summary: Deactivate an account
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/doctors/pending:
    get:
      description: Applications awaiting a decision, oldest first.
      operationId: AdminController_pending
      parameters: []
      responses:
        '200':
          description: Doctors awaiting review.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DoctorDetailDto'
      summary: The doctor approval queue
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/doctors/{id}/approve:
    post:
      description: >-
        Until this happens the doctor is absent from the public directory and
        cannot be booked.
      operationId: AdminController_approve
      parameters:
        - name: id
          required: true
          in: path
          description: The doctor profile to approve.
          schema:
            type: string
      responses:
        '204':
          description: The doctor is approved.
      summary: Approve a doctor
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/doctors/{id}/reject:
    post:
      description: >-
        The stated reason is shown to the doctor on their own profile. A
        rejection is not revisited by the review queue: the profile stays
        rejected unless an administrator later approves it.
      operationId: AdminController_reject
      parameters:
        - name: id
          required: true
          in: path
          description: The doctor profile to reject.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RejectDoctorDto'
      responses:
        '204':
          description: The doctor is rejected.
      summary: Reject a doctor
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/doctors/{id}/specializations:
    post:
      description: >-
        The only way the list changes once submitted: doctors may not invent a
        specialization, so an administrator corrects it.
      operationId: AdminController_specializations
      parameters:
        - name: id
          required: true
          in: path
          description: The doctor profile to correct.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSpecializationsDto'
      responses:
        '204':
          description: The specializations were updated.
      summary: Correct a doctor’s specializations
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/appointments:
    get:
      operationId: AdminController_appointments
      parameters:
        - name: state
          required: false
          in: query
          schema:
            type: string
            enum:
              - SCHEDULED
              - COMPLETED
              - CANCELLED
        - name: from
          required: false
          in: query
          schema:
            type: string
        - name: to
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Matching appointments.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AppointmentDto'
      summary: List appointments across all accounts
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/appointments/{id}/cancel:
    post:
      description: Recorded as cancelled by `ADMIN`, with the reason kept.
      operationId: AdminController_cancelAppointment
      parameters:
        - name: id
          required: true
          in: path
          description: The appointment to cancel.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdminCancelDto'
      responses:
        '201':
          description: The cancelled appointment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentDto'
      summary: Cancel an appointment as an administrator
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/stats:
    get:
      operationId: AdminController_stats
      parameters: []
      responses:
        '200':
          description: The current counts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminStatsDto'
      summary: Dashboard counts
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/admin/audit:
    get:
      description: Only actions that changed something appear here.
      operationId: AdminController_audit
      parameters:
        - name: actorId
          required: false
          in: query
          schema:
            type: string
        - name: action
          required: false
          in: query
          schema:
            type: string
            enum:
              - APPOINTMENT_CANCELLED
              - DOCTOR_APPROVED
              - DOCTOR_REJECTED
              - ACCOUNT_ACTIVATED
              - ACCOUNT_SUSPENDED
              - ACCOUNT_DEACTIVATED
              - DOCTOR_SPECIALIZATION_UPDATED
              - NOTE_DRAFT_GENERATED
              - RECORD_SUMMARY_GENERATED
      responses:
        '200':
          description: Matching audit entries, newest first.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AuditEntryDto'
      summary: The audit log
      tags:
        - admin
      security:
        - session: []
      x-roles:
        - ADMIN
  /api/notifications:
    get:
      description: >-
        Scoped to the caller. No role restriction: every account has a
        notification list.
      operationId: NotificationsController_list
      parameters: []
      responses:
        '200':
          description: The caller’s notifications, newest first.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NotificationDto'
      summary: List the calling account’s notifications
      tags:
        - notifications
      security:
        - session: []
  /api/notifications/unread-count:
    get:
      operationId: NotificationsController_unreadCount
      parameters: []
      responses:
        '200':
          description: How many are still unread.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnreadCountDto'
      summary: The unread badge count
      tags:
        - notifications
      security:
        - session: []
  /api/notifications/reminders:
    get:
      description: The reminders shown on the dashboard, derived rather than stored.
      operationId: NotificationsController_reminders
      parameters: []
      responses:
        '200':
          description: Reminder notifications.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NotificationDto'
      summary: Upcoming appointment reminders
      tags:
        - notifications
      security:
        - session: []
  /api/notifications/{id}/read:
    post:
      operationId: NotificationsController_markRead
      parameters:
        - name: id
          required: true
          in: path
          description: The notification to mark.
          schema:
            type: string
      responses:
        '204':
          description: The notification is now read.
      summary: Mark one notification read
      tags:
        - notifications
      security:
        - session: []
  /api/notifications/read-all:
    post:
      operationId: NotificationsController_markAllRead
      parameters: []
      responses:
        '204':
          description: Every notification is now read.
      summary: Mark every notification read
      tags:
        - notifications
      security:
        - session: []
  /api/health:
    get:
      description: >-
        Public by design. Consumed by the compose healthcheck and by Fly.io,
        neither of which holds a session.
      operationId: HealthController_check
      parameters: []
      responses:
        '200':
          description: The service is up. `database` reports a `SELECT 1`, not a 500.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponseDto'
      summary: Liveness and database reachability
      tags:
        - health
      security: []
info:
  title: reMEDyo API
  description: >-
    The HTTP interface of reMEDyo, a fictional telehealth prototype.


    Every route requires an authenticated session unless it is marked
    otherwise, 

    because authentication is applied globally and opted out of per route.
    Routes 

    restricted to a role carry that restriction in `x-roles`.


    This document is generated from the running application, not written by
    hand.
  version: 0.1.0
  contact: {}
tags: []
servers: []
components:
  securitySchemes:
    session:
      type: apiKey
      in: cookie
      name: remedyo_session
  schemas:
    RegisterPatientDto:
      type: object
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
          maxLength: 128
        fullName:
          type: string
          maxLength: 120
      required:
        - email
        - password
    SessionUserDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          enum:
            - PATIENT
            - DOCTOR
            - ADMIN
          type: string
        status:
          enum:
            - ACTIVE
            - SUSPENDED
            - DEACTIVATED
          type: string
        displayName:
          type: string
          nullable: true
          description: Display name, or null before a profile has been filled in.
        initials:
          type: string
          description: One or two characters, for the avatar.
        profileComplete:
          type: boolean
          description: >-
            Patients: name and date of birth present. Doctors: profile
            submitted.
        approvalState:
          enum:
            - PENDING
            - APPROVED
            - REJECTED
          type: string
          description: Doctors only. Absent for patients.
      required:
        - id
        - email
        - role
        - status
        - displayName
        - initials
        - profileComplete
    RegisterDoctorDto:
      type: object
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
          maxLength: 128
        fullName:
          type: string
          minLength: 2
          maxLength: 120
        specializations:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        licenseNumber:
          type: string
          minLength: 3
          maxLength: 60
        yearsExperience:
          type: number
          minimum: 0
        bio:
          type: string
          maxLength: 2000
      required:
        - email
        - password
        - fullName
        - specializations
        - licenseNumber
    LoginDto:
      type: object
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 1
      required:
        - email
        - password
    LogoutResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: Always true; the cookie is cleared.
      required:
        - ok
    MedicalHistoryEntryDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        kind:
          enum:
            - ALLERGY
            - MEDICATION
            - CONDITION
            - NOTE
          type: string
        description:
          type: string
        recordedAt:
          type: string
          format: date-time
      required:
        - id
        - kind
        - description
        - recordedAt
    PatientProfileDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        fullName:
          type: string
          nullable: true
        dateOfBirth:
          type: string
          nullable: true
          format: date
          description: ISO date, `YYYY-MM-DD`.
        age:
          type: number
          nullable: true
          description: Derived from dateOfBirth, never stored.
        contactNumber:
          type: string
          nullable: true
        weightKg:
          type: number
          nullable: true
        heightCm:
          type: number
          nullable: true
        initials:
          type: string
          description: One or two characters, for the avatar.
        history:
          type: array
          items:
            $ref: '#/components/schemas/MedicalHistoryEntryDto'
      required:
        - id
        - userId
        - fullName
        - dateOfBirth
        - age
        - contactNumber
        - weightKg
        - heightCm
        - initials
        - history
    UpdatePatientProfileDto:
      type: object
      properties:
        fullName:
          type: string
          maxLength: 120
        dateOfBirth:
          type: string
        contactNumber:
          type: string
          maxLength: 40
        weightKg:
          type: number
          minimum: 1
          maximum: 500
        heightCm:
          type: number
          minimum: 20
          maximum: 280
    AddHistoryEntryDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - ALLERGY
            - MEDICATION
            - CONDITION
            - NOTE
        description:
          type: string
          minLength: 2
          maxLength: 500
      required:
        - kind
        - description
    AvailabilityWindowDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        dayOfWeek:
          type: number
          minimum: 0
          maximum: 6
          description: 0 = Sunday .. 6 = Saturday.
        startMinute:
          type: number
          minimum: 0
          maximum: 1439
          description: Minutes from midnight, local to the doctor.
        endMinute:
          type: number
          minimum: 1
          maximum: 1440
          description: Minutes from midnight, local to the doctor.
      required:
        - id
        - dayOfWeek
        - startMinute
        - endMinute
    CreateWindowDto:
      type: object
      properties:
        dayOfWeek:
          type: number
          description: 0 = Sunday .. 6 = Saturday.
          minimum: 0
          maximum: 6
        startMinute:
          type: number
          description: Minutes from midnight, local to the doctor.
          minimum: 0
          maximum: 1439
        endMinute:
          type: number
          description: >-
            Minutes from midnight, local to the doctor. Must be after
            startMinute.
          minimum: 1
          maximum: 1440
      required:
        - dayOfWeek
        - startMinute
        - endMinute
    AvailabilityExceptionDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        date:
          type: string
          format: date
          description: ISO date, marked unavailable.
        reason:
          type: string
          nullable: true
          description: Visible to the owning doctor only.
      required:
        - id
        - date
        - reason
    CreateExceptionDto:
      type: object
      properties:
        date:
          type: string
          description: ISO date, `YYYY-MM-DD`.
        reason:
          type: string
          description: Shown to the doctor only.
          maxLength: 200
      required:
        - date
    SlotDto:
      type: object
      properties:
        startsAt:
          type: string
          format: date-time
          description: ISO-8601 start instant.
        endsAt:
          type: string
          format: date-time
          description: ISO-8601 end instant.
      required:
        - startsAt
        - endsAt
    DoctorDetailDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        fullName:
          type: string
        initials:
          type: string
          description: One or two characters, for the avatar.
        specializations:
          type: array
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        yearsExperience:
          type: number
        consultationFee:
          type: number
        bioExcerpt:
          type: string
          description: A short extract of the doctor’s own biography.
        hasUpcomingAvailability:
          type: boolean
          description: Whether the doctor has any offerable slot in the window searched.
        approvalState:
          enum:
            - PENDING
            - APPROVED
            - REJECTED
          type: string
        bio:
          type: string
        licenseNumber:
          type: string
        nextSlots:
          description: The soonest offerable slots, for the booking page.
          type: array
          items:
            $ref: '#/components/schemas/SlotDto'
        rejectionReason:
          type: string
          nullable: true
          description: Populated only for a doctor whose application was rejected.
      required:
        - id
        - userId
        - fullName
        - initials
        - specializations
        - yearsExperience
        - consultationFee
        - bioExcerpt
        - hasUpcomingAvailability
        - approvalState
        - bio
        - licenseNumber
        - nextSlots
    UpdateDoctorProfileDto:
      type: object
      properties:
        fullName:
          type: string
          minLength: 2
          maxLength: 120
        specializations:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        bio:
          type: string
          maxLength: 2000
        yearsExperience:
          type: number
          minimum: 0
          maximum: 70
        licenseNumber:
          type: string
          maxLength: 60
        consultationFee:
          type: number
          minimum: 0
          maximum: 1000000
    DoctorSummaryDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        fullName:
          type: string
        initials:
          type: string
          description: One or two characters, for the avatar.
        specializations:
          type: array
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        yearsExperience:
          type: number
        consultationFee:
          type: number
        bioExcerpt:
          type: string
          description: A short extract of the doctor’s own biography.
        hasUpcomingAvailability:
          type: boolean
          description: Whether the doctor has any offerable slot in the window searched.
        approvalState:
          enum:
            - PENDING
            - APPROVED
            - REJECTED
          type: string
      required:
        - id
        - userId
        - fullName
        - initials
        - specializations
        - yearsExperience
        - consultationFee
        - bioExcerpt
        - hasUpcomingAvailability
        - approvalState
    AppointmentPartyDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        fullName:
          type: string
        initials:
          type: string
      required:
        - id
        - fullName
        - initials
    AppointmentDoctorDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        fullName:
          type: string
        initials:
          type: string
        specializations:
          type: array
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
      required:
        - id
        - fullName
        - initials
        - specializations
    AppointmentDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        patient:
          $ref: '#/components/schemas/AppointmentPartyDto'
        doctor:
          description: The doctor, with the specializations the booking page shows.
          allOf:
            - $ref: '#/components/schemas/AppointmentDoctorDto'
        startsAt:
          type: string
          format: date-time
        endsAt:
          type: string
          format: date-time
        state:
          enum:
            - SCHEDULED
            - COMPLETED
            - CANCELLED
          type: string
        sessionState:
          enum:
            - SCHEDULED
            - JOINED
            - IN_PROGRESS
            - COMPLETED
          type: string
        reasonForVisit:
          type: string
          description: The patient’s own words when booking.
        cancelledBy:
          nullable: true
          enum:
            - PATIENT
            - DOCTOR
            - ADMIN
          type: string
        cancellationReason:
          type: string
          nullable: true
        missed:
          type: boolean
          description: 'Derived: the join window closed with no completion.'
        hasNote:
          type: boolean
          description: Whether a consultation note has been signed.
        prescriptionCount:
          type: number
      required:
        - id
        - patient
        - doctor
        - startsAt
        - endsAt
        - state
        - sessionState
        - reasonForVisit
        - cancelledBy
        - cancellationReason
        - missed
        - hasNote
        - prescriptionCount
    BookAppointmentDto:
      type: object
      properties:
        doctorId:
          type: string
        startsAt:
          type: string
        reasonForVisit:
          type: string
          minLength: 3
          maxLength: 500
      required:
        - doctorId
        - startsAt
        - reasonForVisit
    RescheduleAppointmentDto:
      type: object
      properties:
        startsAt:
          type: string
      required:
        - startsAt
    CancelAppointmentDto:
      type: object
      properties:
        reason:
          type: string
          maxLength: 500
    ConsultationMessageDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        appointmentId:
          type: string
          format: uuid
        senderId:
          type: string
          format: uuid
        senderName:
          type: string
        body:
          type: string
        sentAt:
          type: string
          format: date-time
      required:
        - id
        - appointmentId
        - senderId
        - senderName
        - body
        - sentAt
    ClinicalContextDto:
      type: object
      properties:
        age:
          type: number
          nullable: true
        allergies:
          nullable: true
          type: array
          items:
            type: string
        medications:
          nullable: true
          type: array
          items:
            type: string
        conditions:
          nullable: true
          type: array
          items:
            type: string
      required:
        - age
        - allergies
        - medications
        - conditions
    ConsultationContextDto:
      type: object
      properties:
        appointment:
          $ref: '#/components/schemas/AppointmentDto'
        sessionState:
          enum:
            - SCHEDULED
            - JOINED
            - IN_PROGRESS
            - COMPLETED
          type: string
        patientJoinedAt:
          type: string
          nullable: true
          format: date-time
        doctorJoinedAt:
          type: string
          nullable: true
          format: date-time
        joinable:
          type: boolean
          description: Whether the join window is open for this caller right now.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/ConsultationMessageDto'
        clinicalContext:
          description: Populated for the doctor only. A patient never receives this.
          allOf:
            - $ref: '#/components/schemas/ClinicalContextDto'
      required:
        - appointment
        - sessionState
        - patientJoinedAt
        - doctorJoinedAt
        - joinable
        - messages
    SendMessageDto:
      type: object
      properties:
        body:
          type: string
          minLength: 1
          maxLength: 2000
      required:
        - body
    ConsultationNoteDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        appointmentId:
          type: string
          format: uuid
        findings:
          type: string
        diagnosis:
          type: string
        recommendations:
          type: string
        followUp:
          type: string
          nullable: true
        authorName:
          type: string
          description: The doctor who signed the note.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
          description: Notes are revised, never deleted.
      required:
        - id
        - appointmentId
        - findings
        - diagnosis
        - recommendations
        - followUp
        - authorName
        - createdAt
        - updatedAt
    PrescriptionDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        appointmentId:
          type: string
          format: uuid
        medication:
          type: string
        dosage:
          type: string
        frequency:
          type: string
        durationDays:
          type: number
        instructions:
          type: string
          nullable: true
        prescriberName:
          type: string
          description: The doctor who issued it.
        issuedAt:
          type: string
          format: date-time
      required:
        - id
        - appointmentId
        - medication
        - dosage
        - frequency
        - durationDays
        - instructions
        - prescriberName
        - issuedAt
    RecordSummaryDto:
      type: object
      properties:
        summary:
          type: string
        model:
          type: string
          description: The model that produced it, so what is on screen can be traced.
        generatedAt:
          type: string
          format: date-time
      required:
        - summary
        - model
        - generatedAt
    MedicalRecordEntryDto:
      type: object
      properties:
        appointment:
          $ref: '#/components/schemas/AppointmentDto'
        note:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/ConsultationNoteDto'
        prescriptions:
          type: array
          items:
            $ref: '#/components/schemas/PrescriptionDto'
        summary:
          nullable: true
          description: >-
            Null when generation is disabled, unavailable, or the summary is
            stale.
          type: object
          allOf:
            - $ref: '#/components/schemas/RecordSummaryDto'
      required:
        - appointment
        - note
        - prescriptions
        - summary
    AssistAvailabilityDto:
      type: object
      properties:
        enabled:
          type: boolean
          description: False when clinical assist is switched off for this deployment.
        model:
          type: string
          nullable: true
          description: The model that would answer, when enabled.
      required:
        - enabled
        - model
    ExtractionCandidateDto:
      type: object
      properties:
        medication:
          type: string
        dosage:
          type: string
        frequency:
          type: string
        durationDays:
          type: number
        instructions:
          type: string
          nullable: true
        sourceMessageId:
          type: string
          format: uuid
          description: The doctor message these values were read from.
        excerpt:
          type: string
          description: The words in that message the values came from.
      required:
        - medication
        - dosage
        - frequency
        - durationDays
        - instructions
        - sourceMessageId
        - excerpt
    NoteDraftDto:
      type: object
      properties:
        findings:
          type: string
        diagnosis:
          type: string
        recommendations:
          type: string
        followUp:
          type: string
          nullable: true
        extractionCandidates:
          description: >-
            Verified prescription values, each traceable to the doctor’s own
            message.
          type: array
          items:
            $ref: '#/components/schemas/ExtractionCandidateDto'
        model:
          type: string
        generatedAt:
          type: string
          format: date-time
      required:
        - findings
        - diagnosis
        - recommendations
        - followUp
        - extractionCandidates
        - model
        - generatedAt
    DraftedNoteResultDto:
      type: object
      properties:
        status:
          enum:
            - drafted
          type: string
          example: drafted
        draft:
          $ref: '#/components/schemas/NoteDraftDto'
      required:
        - status
        - draft
    RefusedNoteResultDto:
      type: object
      properties:
        status:
          enum:
            - refused
          type: string
          example: refused
        reason:
          enum:
            - transcript-empty
            - transcript-too-thin
            - unavailable
          type: string
          description: >-
            Why no draft was produced. All three render as one absent-assistance
            state.
      required:
        - status
        - reason
    UpsertNoteDto:
      type: object
      properties:
        findings:
          type: string
          minLength: 3
          maxLength: 4000
        diagnosis:
          type: string
          minLength: 3
          maxLength: 2000
        recommendations:
          type: string
          minLength: 3
          maxLength: 4000
        followUp:
          type: string
          maxLength: 1000
        aiAssisted:
          type: boolean
          description: >-
            Whether the doctor started from a generated draft.


            Declared by the authoring interface, because only the form knows:
            the

            presence of a draft row does not tell you the doctor used it. A

            provenance marker, not an integrity control — see design.md D7.
      required:
        - findings
        - diagnosis
        - recommendations
    CreatePrescriptionDto:
      type: object
      properties:
        medication:
          type: string
          minLength: 2
          maxLength: 200
        dosage:
          type: string
          minLength: 1
          maxLength: 100
        frequency:
          type: string
          minLength: 1
          maxLength: 200
        durationDays:
          type: number
          minimum: 1
          maximum: 365
        instructions:
          type: string
          maxLength: 1000
      required:
        - medication
        - dosage
        - frequency
        - durationDays
    SymptomOptionDto:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        emergency:
          type: boolean
          description: >-
            Emergency indicators trigger prominent guidance ahead of any
            suggestion of care.
      required:
        - id
        - label
        - emergency
    MatchIntakeDto:
      type: object
      properties:
        symptomIds:
          description: Identifiers from the symptom catalogue.
          maxItems: 20
          type: array
          items:
            type: string
        freeText:
          type: string
          description: >-
            The patient's own words, matched against the same rules as the
            identifiers.
          maxLength: 1000
        durationDays:
          type: number
          minimum: 0
          maximum: 3650
        severity:
          type: string
          enum:
            - MILD
            - MODERATE
            - SEVERE
    MatchSuggestionDto:
      type: object
      properties:
        doctor:
          $ref: '#/components/schemas/DoctorSummaryDto'
        score:
          type: number
          description: 'Deterministic: the same intake always scores the same.'
        matchedSpecialization:
          enum:
            - GENERAL_PRACTICE
            - CARDIOLOGY
            - DERMATOLOGY
            - PEDIATRICS
            - PSYCHIATRY
            - ORTHOPEDICS
            - NEUROLOGY
            - GASTROENTEROLOGY
            - OBSTETRICS_GYNECOLOGY
            - ENDOCRINOLOGY
            - OPHTHALMOLOGY
            - PULMONOLOGY
            - ENT
          type: string
        reason:
          type: string
          description: Plain language, naming the concerns that produced the match.
      required:
        - doctor
        - score
        - matchedSpecialization
        - reason
    MatchResultDto:
      type: object
      properties:
        suggestions:
          type: array
          items:
            $ref: '#/components/schemas/MatchSuggestionDto'
        fallback:
          type: boolean
          description: True when no rule matched, or no doctor held the matched specialty.
        fallbackReason:
          type: string
          nullable: true
        emergencyWarning:
          type: boolean
          description: >-
            True when the intake contained an emergency indicator. Rendered
            ahead of the suggestions.
      required:
        - suggestions
        - fallback
        - fallbackReason
        - emergencyWarning
    AdminUserRowDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          enum:
            - PATIENT
            - DOCTOR
            - ADMIN
          type: string
        status:
          enum:
            - ACTIVE
            - SUSPENDED
            - DEACTIVATED
          type: string
        fullName:
          type: string
          nullable: true
        initials:
          type: string
        statusReason:
          type: string
          nullable: true
          description: Why the account was suspended or deactivated.
        createdAt:
          type: string
          format: date-time
        approvalState:
          nullable: true
          enum:
            - PENDING
            - APPROVED
            - REJECTED
          type: string
          description: Doctors only. Null for patients and administrators.
      required:
        - id
        - email
        - role
        - status
        - fullName
        - initials
        - statusReason
        - createdAt
        - approvalState
    SuspendAccountDto:
      type: object
      properties:
        reason:
          type: string
          description: 'Mandatory: the admin-console spec rejects a reasonless suspension.'
          minLength: 3
          maxLength: 500
      required:
        - reason
    RejectDoctorDto:
      type: object
      properties:
        reason:
          type: string
          minLength: 3
          maxLength: 500
      required:
        - reason
    UpdateSpecializationsDto:
      type: object
      properties:
        specializations:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - GENERAL_PRACTICE
              - CARDIOLOGY
              - DERMATOLOGY
              - PEDIATRICS
              - PSYCHIATRY
              - ORTHOPEDICS
              - NEUROLOGY
              - GASTROENTEROLOGY
              - OBSTETRICS_GYNECOLOGY
              - ENDOCRINOLOGY
              - OPHTHALMOLOGY
              - PULMONOLOGY
              - ENT
        reason:
          type: string
          maxLength: 500
      required:
        - specializations
    AdminCancelDto:
      type: object
      properties:
        reason:
          type: string
          maxLength: 500
    AdminStatsDto:
      type: object
      properties:
        patients:
          type: number
        doctors:
          type: number
        doctorsPendingReview:
          type: number
          description: Doctors whose application is still awaiting a decision.
        appointmentsByState:
          type: object
          additionalProperties:
            type: integer
          description: >-
            One entry per stored appointment state, including states with a zero
            count.
          example:
            SCHEDULED: 12
            COMPLETED: 40
            CANCELLED: 3
        consultationsCompleted:
          type: number
      required:
        - patients
        - doctors
        - doctorsPendingReview
        - appointmentsByState
        - consultationsCompleted
    AuditEntryDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        actorId:
          type: string
          format: uuid
        actorName:
          type: string
        actorEmail:
          type: string
          format: email
        action:
          enum:
            - ACCOUNT_ACTIVATED
            - ACCOUNT_SUSPENDED
            - ACCOUNT_DEACTIVATED
            - DOCTOR_APPROVED
            - DOCTOR_REJECTED
            - DOCTOR_SPECIALIZATION_UPDATED
            - APPOINTMENT_CANCELLED
            - NOTE_DRAFT_GENERATED
            - RECORD_SUMMARY_GENERATED
          type: string
        targetType:
          type: string
          description: The kind of record acted on, such as `User` or `DoctorProfile`.
        targetId:
          type: string
          format: uuid
        reason:
          type: string
          nullable: true
          description: The administrator’s stated reason, where given.
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - actorId
        - actorName
        - actorEmail
        - action
        - targetType
        - targetId
        - reason
        - createdAt
    NotificationDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          enum:
            - APPOINTMENT_BOOKED
            - APPOINTMENT_RESCHEDULED
            - APPOINTMENT_CANCELLED
            - APPOINTMENT_REMINDER
            - RECORDS_AVAILABLE
            - DOCTOR_APPROVED
            - DOCTOR_REJECTED
            - ACCOUNT_STATUS_CHANGED
          type: string
        title:
          type: string
        body:
          type: string
        link:
          type: string
          nullable: true
          description: The route this notification points at, if any.
        readAt:
          type: string
          nullable: true
          format: date-time
          description: Null while unread.
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - type
        - title
        - body
        - link
        - readAt
        - createdAt
    UnreadCountDto:
      type: object
      properties:
        unread:
          type: number
          description: Notifications addressed to the caller that have not been read.
      required:
        - unread
    HealthResponseDto:
      type: object
      properties:
        status:
          type: string
          example: ok
          description: Always `ok`; a failure is a non-200.
        database:
          type: string
          enum:
            - up
            - down
          description: The result of a `SELECT 1` against the database.
      required:
        - status
        - database
security:
  - session: []
