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.

Clinical form templates give ClinicFlow a schema-driven extensibility point: instead of hardcoding specialty-specific fields, an administrator defines a ClinicalFormTemplate with a JSON Schema definition. The template is then attached to one or more AppointmentTypeDefinition entities as a required form. When a doctor completes an encounter, the system validates every submitted DynamicClinicalDetail payload against its template’s schema before the record is saved.

The ClinicalFormTemplate Entity

ClinicalFormTemplate extends SoftDeletableEntity, so deactivation uses the same soft-delete pattern as doctors — the record remains in the database but is filtered out of active queries.
public class ClinicalFormTemplate : SoftDeletableEntity
{
    /// <summary>
    /// Immutable natural key used by external systems and the frontend.
    /// Changing it would break historical records.
    /// </summary>
    public string Code { get; private set; }

    public string Name        { get; private set; }
    public string Description { get; private set; }

    /// <summary>
    /// JSON string defining the schema, fields, and types required for this template.
    /// Acts as metadata for frontend rendering and backend validation.
    /// </summary>
    public string JsonSchemaDefinition { get; private set; } = "{}";
}
FieldTypeConstraints
CodestringRequired; unique; no public setter — treated as immutable after creation
NamestringRequired; unique
DescriptionstringOptional
JsonSchemaDefinitionstringMust be valid JSON Schema draft-07; defaults to "{}"
The Code field is a natural key — it is what IClinicalDetailRecord.TemplateCode references. Once a template is used in a medical record, changing its Code would orphan those historical records. The field has a private set accessor; it is set once by ClinicalFormTemplate.Create and no public mutation method exposes it afterward.

How Templates Are Attached to Appointment Types

Templates become mandatory for an appointment type through two application-layer commands:
  • AddRequiredTemplateToAppointmentType — associates a ClinicalFormTemplate (by TemplateId) with an AppointmentTypeDefinition. From that point forward, every CompleteMedicalEncounter request for that appointment type must include a DynamicClinicalDetailDto whose TemplateCode matches the attached template.
  • RemoveRequiredTemplateFromAppointmentType — detaches the template, making it optional for future encounters (historical records are unaffected).
public sealed record AddRequiredTemplateToAppointmentTypeCommand(
    Guid AppointmentTypeId,
    Guid TemplateId
) : IRequest;

public sealed record RemoveRequiredTemplateFromAppointmentTypeCommand(
    Guid AppointmentTypeId,
    Guid TemplateId
) : IRequest;
At encounter time, MetadataFormValidationPolicy (an IMedicalRecordValidationPolicy implementation) iterates over AppointmentTypeDefinition.RequiredTemplates and verifies that the submitted Details list contains an entry for every required template code.

Schema Validation Interfaces

Two distinct interfaces guard the schema at different lifecycle stages.

IJsonSchemaDefinitionValidator

Used at template creation time (in CreateClinicalFormTemplateCommandValidator) to verify that the JsonSchemaDefinition string itself is a structurally valid JSON Schema. The implementation lives in the Infrastructure layer.
public interface IJsonSchemaDefinitionValidator
{
    /// <returns>True if the schema is structurally valid; false otherwise.</returns>
    bool IsValidSchema(string schemaDefinition, out string? errorMessage);
}

IJsonSchemaValidator

Used at encounter time (in MedicalEncounterService.AppendClinicalDetail) to validate a submitted JSON data payload against a stored schema.
public interface IJsonSchemaValidator
{
    /// <param name="schemaDefinition">Schema from ClinicalFormTemplate.JsonSchemaDefinition.</param>
    /// <param name="jsonDataPayload">Submitted data from DynamicClinicalDetail.JsonDataPayload.</param>
    /// <param name="errorMessage">Populated when validation fails.</param>
    /// <returns>True if the payload is valid; otherwise false.</returns>
    bool ValidateSchema(
        string schemaDefinition,
        string jsonDataPayload,
        out string? errorMessage);
}
Both interfaces are expected to conform to the JSON Schema draft-07 specification. Implementations reside in the Infrastructure layer to keep the Domain and Application layers free of serialization dependencies.

Commands

CreateClinicalFormTemplate

/// <param name="Code">Unique business key (e.g. "BLOOD_PRESS").</param>
/// <param name="Name">Descriptive name of the template.</param>
/// <param name="Description">Details the template's purpose.</param>
/// <param name="JsonSchemaDefinition">JSON Schema draft-07 string.</param>
public sealed record CreateClinicalFormTemplateCommand(
    string Code,
    string Name,
    string Description,
    string JsonSchemaDefinition
) : IRequest<Guid>;
Returns: Guid — the new ClinicalFormTemplate.Id. The handler checks for duplicate Code and Name before creating the entity. CreateClinicalFormTemplateCommandValidator calls IJsonSchemaDefinitionValidator.IsValidSchema to reject malformed schemas before the command even reaches the handler. If JsonSchemaDefinition is null or whitespace, the domain falls back to "{}" (an empty schema that accepts any JSON object).

UpdateClinicalFormTemplate

Updates Name, Description, and JsonSchemaDefinition for an existing template. The Code is not updatable.
public sealed record UpdateClinicalFormTemplateCommand(
    Guid   TemplateId,
    string Name,
    string Description,
    string JsonSchemaDefinition
) : IRequest;
Internally calls ClinicalFormTemplate.UpdateDetails(name, description) and ClinicalFormTemplate.UpdateSchema(jsonSchemaDefinition) separately, so each can throw its own validation error.

DeactivateClinicalFormTemplate

Soft-deletes the template so it no longer appears in active queries. Throws AlreadyInactive if the template is already deactivated.
public sealed record DeactivateClinicalFormTemplateCommand(Guid TemplateId) : IRequest;

ReactivateClinicalFormTemplate

Restores a soft-deleted template. Throws AlreadyActive if the template is currently active.
public sealed record ReactivateClinicalFormTemplateCommand(Guid TemplateId) : IRequest;

Queries

GetClinicalFormTemplateByCode

Fetches a single template by its natural key Code. This is the query used internally by AddClinicalDetailToMedicalRecordCommandHandler to load the schema for validation.
public sealed record GetClinicalFormTemplateByCodeQuery(string Code)
    : IRequest<ClinicalFormTemplateDto>;

GetAllActiveClinicalFormTemplates

Returns every non-soft-deleted template. Intended for admin UIs and frontend form builders.
public sealed record GetAllActiveClinicalFormTemplatesQuery
    : IRequest<IReadOnlyList<ClinicalFormTemplateDto>>;

ClinicalFormTemplateDto

/// <param name="Code">Unique business key (e.g. "BLOOD_PRESS").</param>
/// <param name="JsonSchemaDefinition">JSON Schema draft-07 string for validating submissions.</param>
/// <param name="IsDeleted">True when the template has been soft-deleted.</param>
public sealed record ClinicalFormTemplateDto(
    Guid   Id,
    string Code,
    string Name,
    string Description,
    string JsonSchemaDefinition,
    bool   IsDeleted
);

Example JSON Schema

The following accordion shows a complete JsonSchemaDefinition for a simple blood-pressure template. The frontend uses this schema to render the form; the backend uses it to validate submitted payloads via IJsonSchemaValidator.
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Blood Pressure Reading",
  "description": "Records a single blood pressure measurement taken during the encounter.",
  "type": "object",
  "required": ["systolic", "diastolic", "pulse"],
  "additionalProperties": false,
  "properties": {
    "systolic": {
      "type": "integer",
      "description": "Systolic pressure in mmHg.",
      "minimum": 50,
      "maximum": 300
    },
    "diastolic": {
      "type": "integer",
      "description": "Diastolic pressure in mmHg.",
      "minimum": 30,
      "maximum": 200
    },
    "pulse": {
      "type": "integer",
      "description": "Heart rate in beats per minute.",
      "minimum": 20,
      "maximum": 300
    },
    "notes": {
      "type": "string",
      "description": "Optional clinical notes.",
      "maxLength": 500
    }
  }
}
A matching DynamicClinicalDetail.JsonDataPayload submitted during an encounter would look like:
{
  "systolic": 120,
  "diastolic": 80,
  "pulse": 72,
  "notes": "Patient was relaxed prior to reading."
}
Submitting a payload that violates the schema (e.g., "systolic": 400) throws a BusinessRuleValidationException with the specific validation error message returned by IJsonSchemaValidator.
Both IJsonSchemaDefinitionValidator and IJsonSchemaValidator explicitly target the JSON Schema draft-07 specification. Infrastructure implementations (e.g., using NJsonSchema or JsonSchema.Net) must conform to draft-07 semantics. Using a different draft may cause silent validation discrepancies.

Soft-Delete and Lifecycle

Templates with IsDeleted = false appear in GetAllActiveClinicalFormTemplates and can be attached to appointment types. New DynamicClinicalDetail entries can reference their Code.

Build docs developers (and LLMs) love