Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0Crazy-0/ClinicFlow/llms.txt

Use this file to discover all available pages before exploring further.

A MedicalRecord is the permanent, structured output of a clinical encounter. It ties together the patient, the attending doctor, and the appointment, and it holds an ordered collection of IClinicalDetailRecord entries — each a JSON payload validated against a ClinicalFormTemplate schema. Records are created and completed atomically through MedicalEncounterService, which also advances the appointment lifecycle.

The MedicalRecord Entity

public class MedicalRecord : BaseEntity
{
    public Guid PatientId      { get; init; }
    public Guid DoctorId       { get; init; }
    public Guid AppointmentId  { get; init; }

    /// <summary>
    /// Primary symptom or reason for the visit as reported by the patient.
    /// </summary>
    public string ChiefComplaint { get; private set; }

    public IReadOnlyCollection<IClinicalDetailRecord> ClinicalDetails { get; }
}
FieldTypeNotes
PatientIdGuidLinks to the Patient aggregate
DoctorIdGuidLinks to the Doctor aggregate
AppointmentIdGuidOne record per appointment; enforced by repository
ChiefComplaintstringRequired; patient-reported chief symptom
ClinicalDetailsIReadOnlyCollection<IClinicalDetailRecord>Structured form data collected during the encounter

AddClinicalDetail Uniqueness Check

MedicalRecord.AddClinicalDetail enforces that each TemplateCode appears at most once in the record. Attempting to add a second entry with the same code throws DetailAlreadyExists:
internal void AddClinicalDetail(IClinicalDetailRecord detail)
{
    if (_clinicalDetails.Any(d => d.TemplateCode == detail.TemplateCode))
        throw new DomainValidationException(DomainErrors.MedicalEncounter.DetailAlreadyExists);

    _clinicalDetails.Add(detail);
}
MedicalRecord.Create is internal — the only authorised caller is MedicalEncounterService.InitiateMedicalRecord.

Dynamic Clinical Detail Pattern

Clinical details use a two-type pattern: a domain interface that describes the contract, and a concrete EF Core–mapped entity that stores it.

IClinicalDetailRecord

public interface IClinicalDetailRecord
{
    /// <summary>The code of the template this record is fulfilling.</summary>
    string TemplateCode { get; }

    /// <summary>The standardised JSON payload representing the clinical record.</summary>
    string JsonDataPayload { get; }
}

DynamicClinicalDetail

DynamicClinicalDetail is the concrete implementation persisted to the database. It stores any template’s data as a raw JSON string, keeping the domain free of speciality-specific types.
public class DynamicClinicalDetail : BaseEntity, IClinicalDetailRecord
{
    public string TemplateCode    { get; private set; }
    public string JsonDataPayload { get; private set; }

    public static DynamicClinicalDetail Create(string templateCode, string jsonDataPayload)
        => new(templateCode, jsonDataPayload);
}
At encounter time, each DynamicClinicalDetail is validated against the JsonSchemaDefinition of the matching ClinicalFormTemplate before it is appended to the record.

MedicalEncounterService

MedicalEncounterService is the domain service that orchestrates the entire encounter lifecycle. It is an instance service (not static) that accepts IEnumerable<IMedicalRecordValidationPolicy> and IJsonSchemaValidator through constructor injection. It exposes three entry points:
1

InitiateMedicalRecord (static)

Creates a MedicalRecord shell from an in-progress appointment. Throws AppointmentNotInProgress if the appointment is not in InProgress status.
public static MedicalRecord InitiateMedicalRecord(
    Appointment appointment,
    string chiefComplaint)
{
    if (appointment.Status is not AppointmentStatus.InProgress)
        throw new BusinessRuleValidationException(
            DomainErrors.MedicalEncounter.AppointmentNotInProgress);

    return MedicalRecord.Create(
        appointment.PatientId,
        appointment.DoctorId,
        appointment.Id,
        chiefComplaint);
}
2

ValidateAndCompleteRecord (instance)

Runs all registered IMedicalRecordValidationPolicy instances (e.g., the required-template policy), appends every provided IClinicalDetailRecord to the record, and calls Appointment.Complete(completedAt).
public void ValidateAndCompleteRecord(
    MedicalRecord record,
    MedicalEncounterContext context)
{
    // 1. Verify doctor and appointment identities match the record
    // 2. Run all IMedicalRecordValidationPolicy instances
    foreach (var policy in policies)
        policy.Validate(context.AppointmentTypeDefinition, context.ProvidedDetails);

    // 3. Append clinical details (uniqueness enforced per TemplateCode)
    foreach (var detail in context.ProvidedDetails)
        record.AddClinicalDetail(detail);

    // 4. Advance appointment status
    context.Appointment.Complete(context.CompletedAt);
}
3

AppendClinicalDetail (instance)

Used by AddClinicalDetailToMedicalRecord to add a single detail to an existing record. Validates that TemplateCode matches the template, that the payload is non-empty, and then validates the JSON payload against the template’s JsonSchemaDefinition via IJsonSchemaValidator before calling AddClinicalDetail.
public void AppendClinicalDetail(
    MedicalRecord record,
    IClinicalDetailRecord newDetail,
    ClinicalFormTemplate template)
{
    // Verifies TemplateCode matches, payload is non-empty,
    // then validates JSON payload against schema
    ...
    record.AddClinicalDetail(newDetail);
}
The MedicalRecord is created atomically with the appointment completion inside a single Unit of Work transaction. CompleteMedicalEncounterCommandHandler calls both InitiateMedicalRecord and ValidateAndCompleteRecord and then persists everything in a single SaveChangesAsync call, ensuring no partially completed encounter is ever stored.

Template Validation

Clinical details are validated against the RequiredTemplates collection defined on the AppointmentTypeDefinition. At least one registered IMedicalRecordValidationPolicy (the built-in MetadataFormValidationPolicy) enforces that every required template has a matching IClinicalDetailRecord in the provided details list. Any missing template raises a validation error before the record is saved.

Commands

CompleteMedicalEncounter

The primary write command. Creates the MedicalRecord and completes the appointment in one transaction.
public record DynamicClinicalDetailDto(string TemplateCode, string JsonDataPayload);

public sealed record CompleteMedicalEncounterCommand(
    Guid PatientId,
    Guid DoctorId,
    Guid AppointmentId,
    string ChiefComplaint,
    IReadOnlyList<DynamicClinicalDetailDto> Details
) : IRequest<Guid>;
Returns: Guid — the new MedicalRecord.Id. Handler flow:
  1. Loads Doctor, Appointment, and AppointmentTypeDefinition.
  2. Builds a list of DynamicClinicalDetail from Details.
  3. Calls MedicalEncounterService.InitiateMedicalRecord → then ValidateAndCompleteRecord.
  4. Persists MedicalRecord and the updated Appointment in one SaveChangesAsync.

AddClinicalDetailToMedicalRecord

Adds a single structured detail to an existing record. This is useful when a doctor needs to attach a supplementary form (e.g., lab results) after the initial encounter record is created but before it is formally closed.
public sealed record AddClinicalDetailToMedicalRecordCommand(
    Guid   MedicalRecordId,
    string TemplateCode,
    string JsonDataPayload
) : IRequest;
The handler fetches both the MedicalRecord and the ClinicalFormTemplate by TemplateCode, creates a DynamicClinicalDetail, and delegates to MedicalEncounterService.AppendClinicalDetail for schema validation before saving.

Immutability After Completion

Once Appointment.Complete has been called, the appointment transitions to Completed status and can no longer be moved back to InProgress. Because MedicalRecord.Create requires AppointmentStatus.InProgress, and the repository enforces a one-record-per-appointment constraint, it is impossible to create a second record or reopen a completed one.

Queries

GetMedicalRecordByAppointmentId

Returns a single MedicalRecordDto for the given appointment.
public sealed record GetMedicalRecordByAppointmentIdQuery(
    Guid AppointmentId
) : IRequest<MedicalRecordDto>;

GetMedicalRecordsByPatientId

Paginated history for a patient — ordered by appointment date.
public sealed record GetMedicalRecordsByPatientIdQuery(
    Guid PatientId,
    int  PageNumber,
    int  PageSize
) : IRequest<PaginatedList<MedicalRecordDto>>;

GetMedicalRecordsByDoctorId

Paginated encounter history authored by a specific doctor.
public sealed record GetMedicalRecordsByDoctorIdQuery(
    Guid DoctorId,
    int  PageNumber,
    int  PageSize
) : IRequest<PaginatedList<MedicalRecordDto>>;

GetClinicalDetailByTemplateCode

Fetches a single ClinicalDetailDto from a record by its TemplateCode. Useful for rendering a specific form’s data in the UI.

MedicalRecordDto

public sealed record MedicalRecordDto(
    Guid   Id,
    Guid   PatientId,
    Guid   DoctorId,
    Guid   AppointmentId,
    string ChiefComplaint,
    IReadOnlyList<ClinicalDetailDto> ClinicalDetails
);

/// <param name="TemplateCode">
///   Unique business key of the template (e.g. "BLOOD_PRESS").
/// </param>
/// <param name="JsonDataPayload">
///   Raw JSON payload containing form values, validated against the template's schema.
/// </param>
public sealed record ClinicalDetailDto(string TemplateCode, string JsonDataPayload);

Build docs developers (and LLMs) love