Skip to content

Objects and behaviours

Objects hold clinically meaningful state and expose clinically meaningful behaviour.

class StudySite {
identity: StudySiteIdentity;
study: StudyReference;
facility: FacilityReference;
country: StudyCountryReference;
participation: SiteParticipation;
activationHistory: SiteActivationDecision[];
select(decision: SiteSelectionDecision): SiteSelected;
beginStartup(plan: SiteStartupPlan): SiteStartupStarted;
activate(assessment: SiteReadinessAssessment): SiteActivated;
openEnrollment(decision: EnrollmentOpeningDecision): SiteOpenedForEnrollment;
suspendEnrollment(reason: SuspensionReason): SiteEnrollmentSuspended;
close(decision: SiteClosureDecision): StudySiteClosed;
}

Value objects prevent clinical meaning from degrading into primitive strings and numbers.

class VisitWindow {
target: ClinicalDate;
earliest: ClinicalDate;
latest: ClinicalDate;
contains(date: ClinicalDate): boolean;
classify(date: ClinicalDate): 'early' | 'in-window' | 'late';
}

Other foundational values include:

  • ClinicalDate, PartialClinicalDate, and ClinicalDateTime
  • CodedTerm and TerminologyReleaseReference
  • Measurement, Quantity, Unit, and ReferenceRange
  • VisitWindow and TimingConstraint
  • Reason, Rationale, and Comment
  • ConsentScope and PermittedPurpose
  • BlindClassification and PrivacyClassification
  • ProtocolVersionReference and DefinitionRevisionReference

Some decisions require several objects and should not be forced into one entity.

class ParticipantEligibilityPolicy {
assess(
criteria: EligibilityCriterion[],
evidence: EligibilityEvidence,
protocol: ProtocolVersionReference,
): EligibilityAssessment;
}
class SiteReadinessPolicy {
assess(
site: StudySite,
approvals: ApplicableApprovalSet,
agreement: ExecutedAgreement,
qualifications: QualificationSet,
requiredEvidence: EssentialEvidenceSet,
): SiteReadinessAssessment;
}

An action describes intent. The object or policy determines the outcome.

Action Object / policy Outcome
────────────────────────────────────────────────────────────────────
ApproveProtocolVersion -> Protocol -> ProtocolVersionApproved
ActivateStudySite -> StudySite -> StudySiteActivated
DetermineEligibility -> EligibilityPolicy -> EligibilityDecisionMade
EnrollParticipant -> StudyParticipant -> ParticipantEnrolled
RandomizeParticipant -> RandomizationDesign -> TreatmentAssigned
RecordObservation -> ObservationSet -> ObservationRecorded
DispenseKit -> SiteInventory -> KitDispensed
ConfirmDeviation -> DeviationAssessment -> DeviationConfirmed
LockDatabase -> DataFinalization -> DatabaseLocked

Invalid actions produce domain explanations, not infrastructure errors.

type EnrollmentRefusal =
| ConsentNotEffective
| ParticipantNotEligible
| SiteNotOpenForEnrollment
| ProtocolVersionMismatch
| ConflictingEnrollment
| EnrollmentLimitReached;

These refusal objects carry the clinical reason and affected objects. They do not contain HTTP status codes or UI messages.

The domain library should determine:

  • whether an action is valid;
  • which invariant would be violated;
  • what state changes conceptually;
  • which domain outcome occurred;
  • what evidence must accompany the outcome.

The domain library should not determine:

  • where the object is stored;
  • which transaction technology is used;
  • how the action reached the library;
  • which screen initiated it;
  • how notifications or integrations are delivered.