@startuml domain_model

left to right direction

!procedure $simple_dictionary($dictionary_name) 

    class $dictionary_name {
        + {ID} code: str
        + {R} name: str
        + {R} definition: str
    }

!endprocedure

$simple_dictionary("TherapeuticAreaAR")
$simple_dictionary("DiagnosisGroupAR")
$simple_dictionary("DiseaseConditionOrIndicationAR")
$simple_dictionary("StudyTypeAR")
$simple_dictionary("TrialIntentTypeAR")
$simple_dictionary("TrialTypeAR")
$simple_dictionary("TrialPhaseAR")
$simple_dictionary("NullValueAR")
$simple_dictionary("AgeUnitAR")


class ClinicalProgrammeAR {
    + {ID} uid: str
    + {R} name: str
    + {static} from_input_values()
}

class ProjectAR {
    + {ID} uid: str
    + {R} project_number: str
    name: str
    + {R} clinical_programme_uid: str
    + description: Optional[str] = None
    + {static} from_input_values()
}
ProjectAR::clinical_programme_uid --> "1" ClinicalProgrammeAR::uid



package StudyDefinition <<Aggregate>> {

    enum StudyStatus {
        DRAFT
        RELEASED
        LOCKED
    }

    class StudyDefinitionAR {
        + {ID} uid: str
        + {R} current_metadata: StudyMetadataVO
        + {R} latest_locked_metadata: Optional[StudyMetadataVO]
        + {R} latest_released_or_locked_metadata: Optional[StudyMetadataVO]
        + {R} all_locked_versions: Sequence[StudyMetadataVO]
        + edit_metadata()
        + lock()
        + unlock()
        + release()
        + mark_deleted()
        + get_specific_locked_metadata_version(int): StudyMetadataVO
        + {static} from_initial_values(): StudyDefinitionAR
    }
    StudyDefinitionAR::current_metadata --> "1" StudyMetadataVO
    StudyDefinitionAR::latest_locked_metadata --> "0..1" StudyMetadataVO
    StudyDefinitionAR::latest_released_or_locked_metadata --> "0..1" StudyMetadataVO
    StudyDefinitionAR::all_locked_versions --> "*" StudyMetadataVO
    StudyDefinitionAR::get_specific_locked_metadata_version ..> StudyMetadataVO

    class StudyMetadataVO {
        + ver_metadata: StudyVersionMetadataVO
        + high_level_study_design: HighLevelStudyDesignVO
        + study_population: StudyPopulationVO
        + id_metadata: StudyIdentificationMetadataVO
        + validate()
    }
    StudyMetadataVO::ver_metadata --> "1" StudyVersionMetadataVO
    StudyMetadataVO::id_metadata --> "1" StudyIdentificationMetadataVO
    StudyMetadataVO::study_population --> "1" StudyPopulationVO 
    StudyMetadataVO::high_level_study_design --> "1" HighLevelStudyDesignVO

    class StudyVersionMetadataVO {
        + study_status: StudyStatus
        + version_timestamp: datetime
        + locked_version_number: Optional[int]
        + locked_version_info: Optional[str]
        + locked_version_author: Optional[str]
        + validate()
    }
    StudyVersionMetadataVO::study_status -[norank]-> "1" StudyStatus
    'StudyStatus "1" <-- VersionMetadataVO::study_status

    class StudyIdentificationMetadataVO  {
        +   project_number: Optional[str]
        +   study_number: Optional[str]
        +   study_acronym: Optional[str]
        +   study_id_prefix: Optional[str]
        +   study_id: Optional[str]
        +   registry_identifiers: RegistryIdentifiersVO
        +   validate()
        +   {static} from_input_values(): StudyIdentificationMetadataVO
    }
    StudyIdentificationMetadataVO::project_number --> "0..1" ProjectAR::project_number
    StudyIdentificationMetadataVO::registry_identifiers -[norank]-> "1" RegistryIdentifiersVO

    class RegistryIdentifiersVO {
        + ct_gov_id: Optional[str]
        + eudract_id: Optional[str]
        + universal_trial_number_utn: Optional[str]
        + japanese_trial_registry_id_japic: Optional[str]
        + investigational_new_drug_application_number_ind: Optional[str]
        + ct_gov_id_null_value_code= Optional[str]
        + eudract_id_null_value_code= Optional[str]
        + universal_trial_number_utn_null_value_code= Optional[str]
        + japanese_trial_registry_id_japic_null_value_code= Optional[str]
        + investigational_new_drug_application_number_ind_null_value_code= Optional[str]
        + validate()
        + {static} from_input_values(): RegistryIdentifiersVO

    }


    class HighLevelStudyDesignVO {
            + study_type_code: Optional[str]
            + study_type_null_value_code: Optional[str]
            + trial_intent_type_codes: Sequence[str]
            + trial_intent_type_null_value_code: Optional[str]
            + trial_phase_code: Optional[str]
            + trial_phase_null_value_code: Optional[str]
            + is_extension_trial: Optional[bool]
            + is_extension_trial_null_value_code: Optional[str]
            + is_adaptive_design: Optional[bool]
            + is_adaptive_design_null_value_code: Optional[str]
            + study_stop_rules: Optional[str]
            + study_stop_rules_null_value_code: Optional[str]
            + validate()
            + {static} from_input_values(): HighLevelStudyDesignVO
    }
    HighLevelStudyDesignVO::study_stop_rules_null_value_code -[norank]-> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::study_type_code --> "0..1" StudyTypeAR::code
    HighLevelStudyDesignVO::study_type_null_value_code --> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::trial_phase_code --> "0..1" TrialPhaseAR::code
    HighLevelStudyDesignVO::trial_phase_null_value_code --> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::trial_type_codes --> "*" TrialTypeAR::code
    HighLevelStudyDesignVO::trial_type_null_value_code --> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::trial_intent_type_codes --> "*" TrialIntentTypeAR::code
    HighLevelStudyDesignVO::trial_intent_type_null_value_code --> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::is_adaptive_design_null_value_code --> "0..1" NullValueAR::code
    HighLevelStudyDesignVO::is_extension_trial_null_value_code --> "0..1" NullValueAR::code


    class StudyPopulationVO {
        + therapeutic_area_codes: Sequence[str]
        + therapeutic_area_null_value_code: Optional[str]
        + disease_condition_or_indication_codes: Sequence[str]
        + disease_condition_or_indication_null_value_code: Optional[str]
        + diagnosis_group_codes: Sequence[str]
        + diagnosis_group_null_value_code: Optional[str]
        + rare_disease_indicator: Optional[bool]
        + rare_disease_indicator_null_value_code: Optional[str]
        + healthy_subject_indicator: Optional[bool]
        + healthy_subject_indicator_null_value_code: Optional[str]
        + planned_minimum_age_of_subjects: Optional[SubjectAgeVO]
        + planned_minimum_age_of_subjects_null_value_code: Optional[str]
        + planned_maximum_age_of_subjects: Optional[SubjectAgeVO]
        + planned_maximum_age_of_subjects_null_value_code: Optional[str]
        + validate()
        + {static} from_input_values(): StudyPopulationVO
    }
    StudyPopulationVO::therapeutic_area_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::therapeutic_area_codes --> "*" TherapeuticAreaAR::code
    StudyPopulationVO::diagnosis_group_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::diagnosis_group_codes --> "*" DiagnosisGroupAR::code
    StudyPopulationVO::disease_condition_or_indication_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::disease_condition_or_indication_codes --> "*" DiseaseConditionOrIndicationAR::code
    StudyPopulationVO::rare_disease_indicator_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::healthy_subject_indicator_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::planned_maximum_age_of_subjects_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::planned_minimum_age_of_subjects_null_value_code --> "0..1" NullValueAR::code
    StudyPopulationVO::planned_maximum_age_of_subjects -[norank]-> "0..1" SubjectAgeVO
    StudyPopulationVO::planned_minimum_age_of_subjects -[norank]-> "0..1" SubjectAgeVO


    class SubjectAgeVO {
        + age_value: float
        + age_unit_code: str
        + validate()
        + {static} from_input_values(): SubjectAgeVO
    }
    SubjectAgeVO::age_unit_code --> "1" AgeUnitAR::code

}

class TemplateVO {
    + name: str
    + parameter_names: Sequence[str]
    + {static} from_input_values(name: str, parameter_name_exists_callback: Callable[[str], bool]) -> TemplateVO
}

class LibraryItemMetadataVO {
    + version_major: int
    + version_minor: int
    + version_author: str
    + version_start_date: datetime
    + version_comment: str
    + version_status: LibraryItemStatus
    + allowed_actions: AbstractSet[LibraryItemAction]
    + {static} initial_version_metadata(version_author: str, version_comment: str) -> VersionMetadataVO
    + new_draft_version(version_author: str, version_comment: str) -> VersionMetadataVO
    + new_final_version(version_author: str, version_comment: str) -> VersionMetadataVO
    + new_retired_version(version_author: str, version_comment: str) -> VersionMetadataVO
}
LibraryItemMetadataVO::version_status --> LibraryItemStatus
LibraryItemMetadataVO::allowed_actions --> "*" LibraryItemAction

package TimeframeTemplate << Aggregate >> {

    class TimeframeTemplateAR {
        + {ID} uid: str
        + {R} template_value: TemplateVO
        + {R} item_metadata: LibraryItemMetadataVO
        + {static} from_input_values(template_value: TemplateVO) -> TimeframeTemplateAR
        + edit_template_value(template_value: TemplateVO, version_author: str, version_comment: str) -> None
        + approve(version_author: str, version_comment: str) -> None
        + new_draft_version(version_author: str, version_comment: str) -> None
        + deactivate(version_author: str, version_comment: str) -> None
        + activate(version_author: str, version_comment: str) -> None
    }
    TimeframeTemplateAR::template_value --> "1" TemplateVO
    TimeframeTemplateAR::item_metadata --> "1" LibraryItemMetadataVO

}

class ParameterValueEntryVO {
    + parameter_name: str
    + parameter_values: Sequence[str]
    + conjunction: str
    + {static} from_input_values(parameter_name: str, parameter_values: Sequence[str], conjunction: str, parameter_name_and_value_valid_callback: Callable[[str, str], bool], conjunction_exists_callback: Callable[[str], bool]) -> ParameterValueEntryVO
}


class TemplateBasedTextVO {
    + name: str
    + parameter_value_entries: Sequence[ParameterValueEntryVO]
    + {static} from_input_values(template_value: TemplateVO, parameter_value_entries: Sequence[ParameterValueEntryVO]) -> TemplateBasedTextVO
}
TemplateBasedTextVO::parameter_value_entries --> "*" ParameterValueEntryVO

enum LibraryItemAction {
    EDIT,
    NEW_DRAFT,
    ACTIVATE,
    DEACTIVATE,
    APPROVE
}

enum LibraryItemStatus {
    DRAFT,
    FINAL,
    RETIRED
}

package Timeframe << Aggregate >> {

    class TimeframeAR {
        + {ID} uid: str
        + {R} item_value: TemplateBasedTextVO
        + {R} item_metadata: LibraryItemMetadataVO
        + {R} library_name: str
        + {R} is_editable: bool
        + {R} allowed_actions: AbstractSet[LibraryItemAction]

        + {static} from_input_values(template_value: TemplateVO) -> TimeframeTemplateAR
        + edit_template_value(template_value: TemplateVO, version_author: str, version_comment: str) -> None
        + approve(version_author: str, version_comment: str) -> None
        + new_draft_version(version_author: str, version_comment: str) -> None
        + deactivate(version_author: str, version_comment: str) -> None
        + activate(version_author: str, version_comment: str) -> None
    }
    TimeframeAR::template_value --> "1" TemplateBasedTextVO
    TimeframeAR::item_metadata --> "1" LibraryItemMetadataVO


}


package StudySelectionObjectives <<Aggregate>> {

    class StudySelectionObjectiveVO {
        + objective_uid: str
        + objective_level: Optional[str]
        + selection_date: datetime.datetime

        + {static} from_input_values()
        + validate()
    }

    class StudySelectionObjectivesAR {
        + {ID} study_uid: str
        + {R} study_objectives_selection: Sequence[StudySelectionObjectiveVO]

        + get_specific_objective_selection()
        + add_objective_selection()
    }
    StudySelectionObjectivesAR::study_objectives_selection --> "*" StudySelectionObjectiveVO
    StudySelectionObjectivesAR::study_uid -> "1" StudyDefinitionAR::uid

}

legend
    = Stereotypes/Patterns designation

    * **Aggregate roots**: classes named with AR suffix
    * **Value objects**: classed named with VO suffix
    * **Entities (non-aggregate root)**: classes without stereotype nor any particular name suffix
    * **Implicit data types**: information concepts that obviously exist in the code but are not made explict (should be corrected in my opinion) - modelled as classes with //<<Implicit>>// stereotype.
      (Note: the structure of those types is a guess work, since there's no explicit definition.)
    * **Read-only properties**: designated with //{R}// (designation skipped for Value Objects properties, since they are all read-only by definition)
    * **Identity values**: identity values of entities (esp. aggregate roots) designated with //{ID}//

    = Other conventions/assumptions

    * only public interface modelled
    * methods/properties meant solely for repository integration and representation mappings (not related to business logic) are skipped in the model

end legend

@enduml