aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuidomain
diff options
context:
space:
mode:
authorvlad <vlad@driver.xyz>2017-06-13 16:12:20 -0700
committervlad <vlad@driver.xyz>2017-06-13 16:12:20 -0700
commitcd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c (patch)
tree062e8dad1a1513e26b0fd08b1742d6ff2ee874f7 /src/main/scala/xyz/driver/pdsuidomain
parent0000a65ab4479a2a40e2d6468036438e9705b4aa (diff)
downloadrest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.tar.gz
rest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.tar.bz2
rest-query-cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c.zip
Adding domain entitiesv0.1.0
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuidomain')
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala21
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/CaseId.scala10
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Criterion.scala55
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala42
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala153
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala48
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Hypothesis.scala16
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala48
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Keyword.scala31
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Label.scala34
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala164
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala34
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala63
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala155
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala18
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala38
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientOrderId.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala33
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RecordRequestId.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala102
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala26
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala51
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala60
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala29
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala100
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala143
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/storage/MedicalRecordDocumentStorage.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/storage/RequestStorage.scala52
37 files changed, 1836 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala
new file mode 100644
index 0000000..17a913d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala
@@ -0,0 +1,21 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuicommon.logging._
+
+case class Arm(id: LongId[Arm],
+ name: String,
+ originalName: String,
+ trialId: StringId[Trial],
+ deleted: Option[LocalDateTime] = None)
+
+object Arm {
+
+ implicit def toPhiString(x: Arm): PhiString = {
+ import x._
+ phi"Arm(id=$id, name=${Unsafe(x.name)}, trialId=${Unsafe(x.trialId)})"
+ }
+}
+
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/CaseId.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/CaseId.scala
new file mode 100644
index 0000000..751545e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/CaseId.scala
@@ -0,0 +1,10 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.util.UUID
+
+final case class CaseId(id: String)
+
+object CaseId {
+
+ def apply() = new CaseId(UUID.randomUUID().toString)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Criterion.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Criterion.scala
new file mode 100644
index 0000000..0dfb33f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Criterion.scala
@@ -0,0 +1,55 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Criterion.Meta.Evidence
+
+final case class Criterion(id: LongId[Criterion],
+ trialId: StringId[Trial],
+ text: Option[String],
+ isCompound: Boolean,
+ meta: String) {
+
+ def isValid(): Boolean = text.nonEmpty && Option(meta).isDefined
+}
+
+object Criterion {
+
+ final case class Meta(evidence: Evidence)
+
+ object Meta {
+ final case class Evidence(pageRatio: Double, start: TextLayerPosition, end: TextLayerPosition)
+ final case class TextLayerPosition(page: Integer, index: Integer, offset: Integer)
+ }
+
+ implicit def toPhiString(x: Criterion): PhiString = {
+ import x._
+ phi"Criterion(id=$id, trialId=$trialId, isCompound=$isCompound)"
+ }
+}
+
+final case class CriterionArm(criterionId: LongId[Criterion], armId: LongId[Arm])
+
+object CriterionArm {
+
+ implicit def toPhiString(x: CriterionArm): PhiString = {
+ import x._
+ phi"CriterionArm(criterionId=$criterionId, armId=$armId)"
+ }
+}
+
+final case class CriterionLabel(id: LongId[CriterionLabel],
+ labelId: Option[LongId[Label]],
+ criterionId: LongId[Criterion],
+ categoryId: Option[LongId[Category]],
+ value: Option[Boolean],
+ isDefining: Boolean)
+
+object CriterionLabel {
+
+ implicit def toPhiString(x: CriterionLabel): PhiString = {
+ import x._
+ phi"CriterionLabel(id=$id, labelId=$labelId, criterionId=$criterionId, " +
+ phi"categoryId=$categoryId, value=$value, isDefining=$isDefining)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala
new file mode 100644
index 0000000..c9d1d88
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala
@@ -0,0 +1,42 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.UuidId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+import xyz.driver.pdsuidomain.entities.DirectReport.ReportType
+
+object DirectReport {
+
+ sealed trait ReportType extends Product with Serializable {
+ def oneOf(xs: ReportType*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[ReportType]): Boolean = xs.contains(this)
+ }
+
+ object ReportType {
+ case object IHC extends ReportType
+ case object DNA extends ReportType
+ case object CFDNA extends ReportType
+
+ val All = Set(IHC, DNA, CFDNA)
+ implicit def toPhiString(x: ReportType): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ implicit def toPhiString(x: DirectReport): PhiString = {
+ import x._
+ phi"DirectReport(id=$id, patientId=$patientId, reportType=$reportType, date=${Unsafe(date)}, " +
+ phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(providerType)}, " +
+ phi"providerName=${Unsafe(providerName)})"
+ }
+
+}
+
+case class DirectReport(id: UuidId[DirectReport],
+ patientId: UuidId[Patient],
+ reportType: ReportType,
+ date: LocalDateTime,
+ documentType: String,
+ providerType: String,
+ providerName: String)
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
new file mode 100644
index 0000000..8c2616a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
@@ -0,0 +1,153 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.core.{JsonGenerator, JsonParser}
+import com.fasterxml.jackson.databind._
+import com.fasterxml.jackson.databind.annotation.{JsonDeserialize, JsonSerialize}
+import xyz.driver.pdsuicommon.domain.{LongId, TextJson, User}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+import xyz.driver.pdsuicommon.validation.Validators
+import xyz.driver.pdsuicommon.validation.Validators.Validator
+import xyz.driver.pdsuidomain.entities.Document.Meta
+import xyz.driver.pdsuicommon.compat.Implicits._
+
+
+final case class ProviderType(id: LongId[ProviderType], name: String)
+
+object ProviderType {
+ implicit def toPhiString(x: ProviderType): PhiString = {
+ import x._
+ phi"ProviderType(id=$id, category=${Unsafe(name)})"
+ }
+}
+
+final case class DocumentType(id: LongId[DocumentType], name: String)
+
+object DocumentType {
+ implicit def toPhiString(x: DocumentType): PhiString = {
+ import x._
+ phi"DocumentType(id=$id, name=${Unsafe(name)})"
+ }
+}
+
+object Document {
+
+ case class Meta(predicted: Option[Boolean], startPage: Double, endPage: Double) {
+ /**
+ * Return a regular meta: this meta is considered as not predicted
+ */
+ def confirmed: Meta = copy(predicted = predicted.map(_ => false))
+ }
+
+ class DocumentStatusSerializer extends JsonSerializer[Status] {
+ def serialize(value: Status, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
+ gen.writeString(value.toString.toUpperCase)
+ }
+ }
+
+ class DocumentStatusDeserializer extends JsonDeserializer[Status] {
+ def deserialize(parser: JsonParser, context: DeserializationContext): Status = {
+ val value = parser.getValueAsString
+ Option(value).fold[Document.Status](Status.New /* Default document status */) { v =>
+ Status.All.find(_.toString.toUpperCase == v) match {
+ case None => throw new RuntimeJsonMappingException(s"$v is not valid Document.Status")
+ case Some(status) => status
+ }
+ }
+ }
+ }
+
+ // Product with Serializable fixes issue:
+ // Set(New, Organized) has type Set[Status with Product with Serializable]
+ @JsonDeserialize(using = classOf[DocumentStatusDeserializer])
+ @JsonSerialize(using = classOf[DocumentStatusSerializer])
+ sealed trait Status extends Product with Serializable {
+
+ def oneOf(xs: Status*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[Status]): Boolean = xs.contains(this)
+
+ }
+ object Status {
+ case object New extends Status
+ case object Organized extends Status
+ case object Extracted extends Status
+ case object Done extends Status
+ case object Flagged extends Status
+ case object Archived extends Status
+
+ val All = Set[Status](New, Organized, Extracted, Done, Flagged, Archived)
+ val AllPrevious = Set[Status](Organized, Extracted)
+
+ implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ implicit def toPhiString(x: Document): PhiString = {
+ import x._
+ phi"Document(id=$id, status=$status, assignee=$assignee, previousAssignee=$previousAssignee, recordId=$recordId)"
+ }
+
+ val validator: Validator[Document, Document] = { input =>
+ for {
+ typeId <- Validators.nonEmpty("typeId")(input.typeId)
+
+ providerTypeId <- Validators.nonEmpty("providerTypeId")(input.providerTypeId)
+
+ meta <- Validators.nonEmpty("meta")(input.meta)
+
+ startDate <- Validators.nonEmpty("startDate")(input.startDate)
+
+ isOrderRight <- input.endDate match {
+ case Some(endDate) if startDate.isAfter(endDate) =>
+ Validators.fail("The start date should be less, than the end one")
+
+ case _ => Validators.success(true)
+ }
+
+ areDatesInThePast <- {
+ val dates = List(input.startDate, input.endDate).flatten
+ val now = LocalDateTime.now()
+ val hasInvalid = dates.exists(_.isAfter(now))
+
+ if (hasInvalid) Validators.fail("Dates should be in the past")
+ else Validators.success(true)
+ }
+ } yield input
+ }
+
+}
+
+@JsonIgnoreProperties(value = Array("valid"))
+case class Document(id: LongId[Document] = LongId(0L),
+ status: Document.Status,
+ previousStatus: Option[Document.Status],
+ assignee: Option[LongId[User]],
+ previousAssignee: Option[LongId[User]],
+ recordId: LongId[MedicalRecord],
+ physician: Option[String],
+ typeId: Option[LongId[DocumentType]], // not null
+ providerName: Option[String], // not null
+ providerTypeId: Option[LongId[ProviderType]], // not null
+ meta: Option[TextJson[Meta]], // not null
+ startDate: Option[LocalDateTime], // not null
+ endDate: Option[LocalDateTime],
+ lastUpdate: LocalDateTime) {
+
+ import Document.Status._
+
+ if (previousStatus.nonEmpty) {
+ assert(AllPrevious.contains(previousStatus.get),
+ s"Previous status has invalid value: ${previousStatus.get}")
+ }
+
+ // TODO: with the current business logic code this constraint sometimes harmful
+ // require(status match {
+ // case Document.Status.New if assignee.isDefined => false
+ // case Document.Status.Done if assignee.isDefined => false
+ // case _ => true
+ // }, "Assignee can't be defined in New or Done statuses")
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala
new file mode 100644
index 0000000..9972142
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala
@@ -0,0 +1,48 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId, TextJson}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.ExtractedData.Meta
+
+final case class ExtractedData(id: LongId[ExtractedData] = LongId(0L),
+ documentId: LongId[Document],
+ keywordId: Option[LongId[Keyword]],
+ evidenceText: Option[String],
+ meta: Option[TextJson[Meta]]) {
+
+ def isValid: Boolean = evidenceText.getOrElse("") != "" && meta.nonEmpty
+
+}
+
+object ExtractedData {
+
+ final case class Meta(keyword: Meta.Keyword, evidence: Meta.Evidence)
+
+ object Meta {
+
+ final case class Evidence(pageRatio: Double, start: TextLayerPosition, end: TextLayerPosition)
+
+ final case class TextLayerPosition(page: Integer, index: Integer, offset: Integer)
+
+ final case class Keyword(page: Integer, pageRatio: Option[Double], index: Integer, sortIndex: String)
+ }
+
+ implicit def toPhiString(x: ExtractedData): PhiString = {
+ import x._
+ phi"ExtractedData(id=$id, documentId=$documentId, keywordId=$keywordId)"
+ }
+}
+
+object ExtractedDataLabel {
+
+ implicit def toPhiString(x: ExtractedDataLabel): PhiString = {
+ import x._
+ phi"ExtractedDataLabel(id=$id, dataId=$dataId, labelId=$labelId, categoryId=$categoryId, value=$value)"
+ }
+}
+
+final case class ExtractedDataLabel(id: LongId[ExtractedDataLabel],
+ dataId: LongId[ExtractedData],
+ labelId: Option[LongId[Label]],
+ categoryId: Option[LongId[Category]],
+ value: Option[FuzzyValue])
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Hypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Hypothesis.scala
new file mode 100644
index 0000000..eb2b95e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Hypothesis.scala
@@ -0,0 +1,16 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.UuidId
+import xyz.driver.pdsuicommon.logging._
+
+final case class Hypothesis(id: UuidId[Hypothesis],
+ name: String,
+ treatmentType: String,
+ description: String)
+
+object Hypothesis {
+ implicit def toPhiString(x: Hypothesis): PhiString = {
+ import x._
+ phi"Hypothesis(id=$id, name=${Unsafe(name)})"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala
new file mode 100644
index 0000000..6ff1a67
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala
@@ -0,0 +1,48 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuicommon.logging._
+
+
+final case class InterventionType(id: LongId[InterventionType], name: String)
+
+object InterventionType {
+ implicit def toPhiString(x: InterventionType): PhiString = {
+ import x._
+ phi"InterventionType(id=$id, name=${Unsafe(name)})"
+ }
+}
+
+final case class InterventionArm(armId: LongId[Arm], interventionId: LongId[Intervention])
+
+object InterventionArm {
+ implicit def toPhiString(x: InterventionArm): PhiString = {
+ import x._
+ phi"InterventionArm(armId=$armId, interventionId=$interventionId)"
+ }
+}
+
+final case class Intervention(id: LongId[Intervention],
+ trialId: StringId[Trial],
+ name: String,
+ originalName: String,
+ typeId: Option[LongId[InterventionType]],
+ originalType: Option[String],
+ description: String,
+ isActive: Boolean)
+
+object Intervention {
+ implicit def toPhiString(x: Intervention): PhiString = {
+ import x._
+ phi"Intervention(id=$id, trialId=$trialId, name=${Unsafe(name)}, typeId=$typeId, isActive=$isActive)"
+ }
+}
+
+final case class InterventionWithArms(intervention: Intervention, arms: List[InterventionArm])
+
+object InterventionWithArms {
+ implicit def toPhiString(x: InterventionWithArms): PhiString = {
+ import x._
+ phi"InterventionWithArms(intervention=$intervention, arms=$arms)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Keyword.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Keyword.scala
new file mode 100644
index 0000000..3683efc
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Keyword.scala
@@ -0,0 +1,31 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+
+final case class Keyword(id: LongId[Keyword], keyword: String)
+
+object Keyword {
+ implicit def toPhiString(x: Keyword): PhiString = {
+ import x._
+ phi"Keyword(id=$id, keyword=${Unsafe(keyword)})"
+ }
+}
+
+final case class KeywordWithLabels(keyword: Keyword, labels: List[Label])
+
+object KeywordWithLabels {
+ implicit def toPhiString(x: KeywordWithLabels): PhiString = {
+ import x._
+ phi"KeywordWithLabels(keyword=$keyword, $labels)"
+ }
+}
+
+final case class KeywordLabel(keywordId: LongId[Keyword], labelId: LongId[Label])
+
+object KeywordLabel {
+ implicit def toPhiString(x: KeywordLabel): PhiString = {
+ import x._
+ phi"KeywordLabel($keywordId, $labelId)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Label.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Label.scala
new file mode 100644
index 0000000..7f9c2aa
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Label.scala
@@ -0,0 +1,34 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+
+final case class Category(id: LongId[Category], name: String)
+
+object Category {
+ implicit def toPhiString(x: Category): PhiString = {
+ import x._
+ phi"Category(id=$id, name=${Unsafe(name)})"
+ }
+}
+
+final case class Label(id: LongId[Label],
+ categoryId: LongId[Category],
+ name: String,
+ description: String)
+
+object Label {
+ implicit def toPhiString(x: Label): PhiString = {
+ import x._
+ phi"Label($id, categoryId=${Unsafe(categoryId)}, name=${Unsafe(name)}, description=${Unsafe(description)})"
+ }
+}
+
+final case class CategoryWithLabels(category: Category, labels: List[Label])
+
+object CategoryWithLabels {
+ implicit def toPhiString(x: CategoryWithLabels): PhiString = {
+ import x._
+ phi"CategoryWithLabels(category=$category, labels=$labels)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala
new file mode 100644
index 0000000..977f4cc
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala
@@ -0,0 +1,164 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.nio.channels.ReadableByteChannel
+import java.time.LocalDateTime
+
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type
+import com.fasterxml.jackson.annotation.{JsonProperty, JsonSubTypes, JsonTypeInfo}
+import xyz.driver.pdsuicommon.domain.{LongId, TextJson, User, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+import xyz.driver.pdsuidomain.entities.MedicalRecord.Meta
+import xyz.driver.pdsuidomain.entities.MedicalRecord.Meta.{Duplicate, Reorder, Rotation}
+
+object MedicalRecord {
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+ @JsonSubTypes(Array(
+ new Type(value = classOf[Duplicate], name = "duplicate"),
+ new Type(value = classOf[Reorder], name = "reorder"),
+ new Type(value = classOf[Rotation], name = "rotation")
+ ))
+ trait Meta {
+ @JsonProperty("type") def metaType: String
+ def predicted: Option[Boolean]
+
+ /**
+ * Return a regular meta: this meta is considered as not predicted
+ */
+ def confirmed: Meta
+ }
+
+ object Meta {
+
+ case class Duplicate(predicted: Option[Boolean],
+ startPage: Double,
+ endPage: Double,
+ startOriginalPage: Double,
+ endOriginalPage: Option[Double]
+ ) extends Meta {
+ override val metaType = "duplicate"
+ override def confirmed: Duplicate = copy(predicted = predicted.map(_ => false))
+ }
+
+ object Duplicate {
+ implicit def toPhiString(x: Duplicate): PhiString = {
+ import x._
+ phi"Duplicate(predicted=${x.predicted}, startPage=${Unsafe(startPage)}, endPage=${Unsafe(endPage)}, " +
+ phi"startOriginalPage=${Unsafe(startOriginalPage)}, endOriginalPage=${Unsafe(endOriginalPage)}"
+ }
+ }
+
+
+ case class Reorder(predicted: Option[Boolean],
+ items: Seq[Int]
+ ) extends Meta {
+ override val metaType = "reorder"
+ override def confirmed: Reorder = copy(predicted = predicted.map(_ => false))
+ }
+
+ object Reorder {
+ implicit def toPhiString(x: Reorder): PhiString = {
+ import x._
+ phi"Reorder(predicted=${x.predicted}, items=${Unsafe(items.toString)})"
+ }
+ }
+
+
+ case class Rotation(predicted: Option[Boolean],
+ items: Map[String, Int]
+ ) extends Meta {
+ override val metaType = "rotation"
+ override def confirmed: Rotation = copy(predicted = predicted.map(_ => false))
+ }
+
+ object Rotation {
+ implicit def toPhiString(x: Rotation): PhiString = {
+ import x._
+ phi"Rotation(predicted=${x.predicted}, items=${Unsafe(items.toString)})"
+ }
+ }
+
+
+ implicit def toPhiString(input: Meta): PhiString = input match {
+ case x: Duplicate => Duplicate.toPhiString(x)
+ case x: Reorder => Reorder.toPhiString(x)
+ case x: Rotation => Rotation.toPhiString(x)
+ }
+
+ }
+
+ // Product with Serializable fixes issue:
+ // Set(New, Cleaned) has type Set[Status with Product with Serializable]
+ sealed trait Status extends Product with Serializable {
+
+ def oneOf(xs: Status*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[Status]): Boolean = xs.contains(this)
+
+ }
+ object Status {
+ case object Unprocessed extends Status
+ case object PreCleaning extends Status
+ case object New extends Status
+ case object Cleaned extends Status
+ case object PreOrganized extends Status
+ case object PreOrganizing extends Status
+ case object Reviewed extends Status
+ case object Organized extends Status
+ case object Done extends Status
+ case object Flagged extends Status
+ case object Archived extends Status
+
+ val All = Set[Status](
+ Unprocessed, PreCleaning, New, Cleaned, PreOrganized, PreOrganizing, Reviewed, Organized, Done, Flagged, Archived
+ )
+
+ val AllPrevious = Set[Status](New, Cleaned, Reviewed, Organized)
+
+ implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ sealed trait PdfSource
+
+ object PdfSource {
+ case object Empty extends PdfSource
+ /** @param createResource Constructor of the resource which is represents the file */
+ case class Channel(createResource: () => ReadableByteChannel) extends PdfSource
+ }
+
+ implicit def toPhiString(x: MedicalRecord): PhiString = {
+ import x._
+ phi"MedicalRecord(id=$id, status=$status, assignee=$assignee, previousAssignee=$previousAssignee)"
+ }
+}
+
+case class MedicalRecord(id: LongId[MedicalRecord],
+ status: MedicalRecord.Status,
+ previousStatus: Option[MedicalRecord.Status],
+ assignee: Option[LongId[User]],
+ previousAssignee: Option[LongId[User]],
+ patientId: UuidId[Patient],
+ requestId: RecordRequestId,
+ disease: String,
+ caseId: Option[CaseId],
+ physician: Option[String],
+ sourceName: String,
+ meta: Option[TextJson[List[Meta]]],
+ predictedMeta: Option[TextJson[List[Meta]]],
+ predictedDocuments: Option[TextJson[List[Document]]],
+ lastUpdate: LocalDateTime) {
+
+ import MedicalRecord.Status._
+
+ if (previousStatus.nonEmpty) {
+ assert(AllPrevious.contains(previousStatus.get),
+ s"Previous status has invalid value: ${previousStatus.get}")
+ }
+
+ // TODO: with the current business logic code this constraint sometimes harmful
+ // require(status match {
+ // case MedicalRecord.Status.Done if assignee.isDefined => false
+ // case _ => true
+ // }, "Assignee can't be defined in Done status")
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala
new file mode 100644
index 0000000..2a73922
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala
@@ -0,0 +1,34 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId, User, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+final case class Message(id: LongId[Message],
+ text: String,
+ lastUpdate: LocalDateTime,
+ userId: LongId[User],
+ isDraft: Boolean,
+ recordId: Option[LongId[MedicalRecord]],
+ documentId: Option[LongId[Document]],
+ patientId: Option[UuidId[Patient]],
+ trialId: Option[StringId[Trial]],
+ startPage: Option[Double],
+ endPage: Option[Double],
+ evidence: Option[String],
+ archiveRequired: Option[Boolean],
+ meta: Option[String])
+
+object Message {
+ implicit def toPhiString(x: Message): PhiString = {
+ import x._
+
+ val entityId = recordId
+ .orElse(documentId)
+ .orElse(patientId)
+ .map(_.toString)
+
+ phi"Message(id=$id, userId=$userId, isDraft=$isDraft, entityId=${Unsafe(entityId)}"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
new file mode 100644
index 0000000..7767db0
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
@@ -0,0 +1,63 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.{LocalDate, LocalDateTime}
+
+import xyz.driver.pdsuicommon.domain.{LongId, User, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+
+object Patient {
+
+ // Product with Serizalizable fixes issue:
+ // Set(New, Verified) has type Set[Status with Product with Serializable]
+ sealed trait Status extends Product with Serializable {
+ def oneOf(xs: Status*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[Status]): Boolean = xs.contains(this)
+ }
+
+ object Status {
+ case object New extends Status
+ case object Verified extends Status
+ case object Reviewed extends Status
+ case object Curated extends Status
+ case object Flagged extends Status
+ case object Done extends Status
+
+ val AllPrevious = Set[Status](
+ New, Verified, Reviewed, Curated
+ )
+
+ val All = Set[Status](
+ New, Verified, Reviewed, Curated, Flagged, Done
+ )
+
+ implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ implicit def toPhiString(x: Patient): PhiString = {
+ import x._
+ phi"Patient(id=$id, status=$status, previousStatus=$previousStatus, " +
+ phi"assignee=$assignee, previousAssignee=$previousAssignee)"
+ }
+}
+
+case class Patient(id: UuidId[Patient],
+ status: Patient.Status,
+ name: String,
+ dob: LocalDate,
+ assignee: Option[LongId[User]],
+ previousStatus: Option[Patient.Status],
+ previousAssignee: Option[LongId[User]],
+ isUpdateRequired: Boolean,
+ condition: String,
+ orderId: PatientOrderId,
+ lastUpdate: LocalDateTime) {
+
+ import Patient.Status._
+
+ if (previousStatus.nonEmpty) {
+ assert(AllPrevious.contains(previousStatus.get),
+ s"Previous status has invalid value: ${previousStatus.get}")
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala
new file mode 100644
index 0000000..8d5af0d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala
@@ -0,0 +1,155 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId, StringId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+object PatientCriterion {
+ implicit def toPhiString(x: PatientCriterion): PhiString = {
+ import x._
+ phi"PatientCriterion(id=$id, patientLabelId=$patientLabelId, trialId=${Unsafe(trialId)}, nctId=${Unsafe(nctId)}, " +
+ phi"criterionId=$criterionId, criterionValue=${Unsafe(criterionValue)}, " +
+ phi"isImplicitMatch=$criterionIsDefining), criterionIsDefining=${Unsafe(criterionIsDefining)}, " +
+ phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, verifiedEligibilityStatus=${Unsafe(verifiedEligibilityStatus)}, " +
+ phi"isVerified=${Unsafe(isVerified)}, lastUpdate=${Unsafe(lastUpdate)}"
+ }
+
+ /**
+ * @see https://driverinc.atlassian.net/wiki/display/MTCH/EV+Business+Process
+ */
+ def getEligibilityStatus(criterionValue: Option[Boolean],
+ primaryValue: Option[FuzzyValue]): Option[FuzzyValue] = {
+ primaryValue match {
+ case None => None
+ case Some(FuzzyValue.Maybe) => Some(FuzzyValue.Maybe)
+ case Some(_) if criterionValue.isEmpty => Some(FuzzyValue.Maybe)
+ case Some(status) => Some(FuzzyValue.fromBoolean(
+ FuzzyValue.fromBoolean(criterionValue.getOrElse(
+ throw new IllegalArgumentException("Criterion should not be empty"))
+ ) == status
+ ))
+ }
+ }
+
+}
+
+/**
+ * @param eligibilityStatus - a value, that selects an eligibility verifier (EV)
+ * @param verifiedEligibilityStatus - a copy of eligibilityStatus, when a patient goes to routes curator (RC)
+ * @param isVerified - is EV selected the eligibilityStatus?
+ */
+final case class PatientCriterion(id: LongId[PatientCriterion],
+ patientLabelId: LongId[PatientLabel],
+ trialId: Long,
+ nctId: StringId[Trial],
+ criterionId: LongId[Criterion],
+ criterionText: String,
+ criterionValue: Option[Boolean],
+ criterionIsDefining: Boolean,
+ eligibilityStatus: Option[FuzzyValue],
+ verifiedEligibilityStatus: Option[FuzzyValue],
+ isVerified: Boolean,
+ isVisible: Boolean,
+ lastUpdate: LocalDateTime) {
+ def isIneligible: Boolean = eligibilityStatus.contains(FuzzyValue.No) && isVerified
+}
+
+object PatientTrialArm {
+
+ implicit def toPhiString(x: PatientTrialArm): PhiString = {
+ import x._
+ phi"PatientTrialArm(armId=$armId, trialArmGroupId=$trialArmGroupId)"
+ }
+}
+
+final case class PatientTrialArm(armId: LongId[Arm], trialArmGroupId: LongId[PatientTrialArmGroup])
+
+object PatientEligibleTrial {
+ implicit def toPhiString(x: PatientEligibleTrial): PhiString = {
+ import x._
+ phi"PatientEligibleTrial(id=$id, patientId=$patientId, trialId=$trialId, hypothesisId=$hypothesisId)"
+ }
+}
+
+final case class PatientEligibleTrial(id: UuidId[PatientEligibleTrial],
+ patientId: UuidId[Patient],
+ trialId: StringId[Trial],
+ hypothesisId: UuidId[Hypothesis])
+
+object PatientTrialArmGroup {
+
+ implicit def toPhiString(x: PatientTrialArmGroup): PhiString = {
+ import x._
+ phi"PatientTrialArmGroup(id=$id, eligibleTrialId=$eligibleTrialId, " +
+ phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, isVerified=$isVerified)"
+ }
+
+ /**
+ * @see https://driverinc.atlassian.net/wiki/display/DMPD/EV+Business+Process
+ */
+ def getEligibilityStatusForEvToRc(criterionList: List[(Option[Boolean], Option[FuzzyValue])]): Option[FuzzyValue] = {
+ def isEligible = {
+ // Eligible, if for all (verified for EV) label-criteria eligibilityStatus=NULL or YES and at least one has status=YES
+ // If method executes from PatientService (when EV submit patient) need to check value PatientCriterion.isVerified
+ val verifiedList = criterionList.filter { case (isVerifiedOpt, _) => isVerifiedOpt.isEmpty || isVerifiedOpt.get }
+ verifiedList.forall { case (_, eligibilityStatus) =>
+ eligibilityStatus.isEmpty || eligibilityStatus.contains(FuzzyValue.Yes)
+ } && verifiedList.exists { case (_, eligibilityStatus) => eligibilityStatus.contains(FuzzyValue.Yes) }
+ }
+
+ if (criterionList.exists { case (isVerified, eligibilityStatus) =>
+ eligibilityStatus.contains(FuzzyValue.No) && (isVerified.isEmpty || isVerified.get)
+ }) { Some(FuzzyValue.No)
+ } else if (criterionList.forall { case (_, eligibilityStatus) => eligibilityStatus.isEmpty }) {
+ None
+ } else if (isEligible) {
+ Some(FuzzyValue.Yes)
+ } else {
+ Some(FuzzyValue.Maybe)
+ }
+ }
+
+ /**
+ * @see https://driverinc.atlassian.net/wiki/display/DMPD/EV+Business+Process
+ */
+ def getEligibilityStatusForRc(criterionList: TraversableOnce[PatientCriterion]): Option[FuzzyValue] = {
+ def isEligible: Boolean = criterionList.forall(_.verifiedEligibilityStatus.contains(FuzzyValue.Yes))
+ def isIneligible: Boolean = criterionList.exists(_.verifiedEligibilityStatus.contains(FuzzyValue.No))
+ def isUnknown: Boolean = criterionList.forall(_.verifiedEligibilityStatus.isEmpty)
+
+ if (isEligible) Some(FuzzyValue.Yes)
+ else if (isIneligible) Some(FuzzyValue.No)
+ else if (isUnknown) None
+ else Some(FuzzyValue.Maybe)
+ }
+}
+
+final case class PatientTrialArmGroup(id: LongId[PatientTrialArmGroup],
+ eligibleTrialId: UuidId[PatientEligibleTrial],
+ eligibilityStatus: Option[FuzzyValue],
+ isVerified: Boolean)
+
+object PatientTrialArmGroupView {
+
+ implicit def toPhiString(x: PatientTrialArmGroupView): PhiString = {
+ import x._
+ phi"PatientTrialArmGroupView(id=$id, patientId=$patientId, trialId=$trialId, hypothesisId=$hypothesisId, " +
+ phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, isVerified=$isVerified)"
+ }
+}
+
+final case class PatientTrialArmGroupView(id: LongId[PatientTrialArmGroup],
+ patientId: UuidId[Patient],
+ trialId: StringId[Trial],
+ hypothesisId: UuidId[Hypothesis],
+ eligibilityStatus: Option[FuzzyValue],
+ isVerified: Boolean) {
+
+ def applyTo(trialArmGroup: PatientTrialArmGroup) = {
+ trialArmGroup.copy(
+ eligibilityStatus = this.eligibilityStatus,
+ isVerified = this.isVerified
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala
new file mode 100644
index 0000000..146b1c3
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala
@@ -0,0 +1,18 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.UuidId
+import xyz.driver.pdsuicommon.logging._
+
+object PatientHypothesis {
+ implicit def toPhiString(x: PatientHypothesis): PhiString = {
+ import x._
+ phi"PatientHypothesis(id=$id, patientId=$patientId, hypothesisId=$hypothesisId, " +
+ phi"rationale=${Unsafe(rationale)}, matchedTrials=${Unsafe(matchedTrials)})"
+ }
+}
+
+case class PatientHypothesis(id: UuidId[PatientHypothesis],
+ patientId: UuidId[Patient],
+ hypothesisId: UuidId[Hypothesis],
+ rationale: Option[String],
+ matchedTrials: Long)
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala
new file mode 100644
index 0000000..633a347
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala
@@ -0,0 +1,38 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+object PatientLabel {
+ implicit def toPhiString(x: PatientLabel): PhiString = {
+ import x._
+ phi"PatientLabel(id=$id, patientId=$patientId, labelId=$labelId, " +
+ phi"score=${Unsafe(score)}, primaryValue=${Unsafe(primaryValue)}, " +
+ phi"verifiedPrimaryValue=${Unsafe(verifiedPrimaryValue)})"
+ }
+}
+
+final case class PatientLabel(id: LongId[PatientLabel],
+ patientId: UuidId[Patient],
+ labelId: LongId[Label],
+ score: Int,
+ primaryValue: Option[FuzzyValue],
+ verifiedPrimaryValue: Option[FuzzyValue],
+ isImplicitMatch: Boolean,
+ isVisible: Boolean)
+
+object PatientLabelEvidence {
+ implicit def toPhiString(x: PatientLabelEvidence): PhiString = {
+ import x._
+ phi"PatientLabelEvidence(id=$id, patientLabelId=$patientLabelId, value=${Unsafe(value)}, " +
+ phi"documentId=$documentId, evidenceId=$evidenceId, reportId=$reportId)"
+ }
+}
+
+final case class PatientLabelEvidence(id: LongId[PatientLabelEvidence],
+ patientLabelId: LongId[PatientLabel],
+ value: FuzzyValue,
+ evidenceText: String,
+ reportId: Option[UuidId[DirectReport]],
+ documentId: Option[LongId[Document]],
+ evidenceId: Option[LongId[ExtractedData]])
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientOrderId.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientOrderId.scala
new file mode 100644
index 0000000..50a97ce
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientOrderId.scala
@@ -0,0 +1,14 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.util.UUID
+
+final case class PatientOrderId(id: UUID) {
+ override def toString: String = id.toString
+}
+
+object PatientOrderId {
+
+ def apply() = new PatientOrderId(UUID.randomUUID())
+
+ def apply(x: String) = new PatientOrderId(UUID.fromString(x))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala
new file mode 100644
index 0000000..88e1a45
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala
@@ -0,0 +1,28 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+case class RawPatientDocument(disease: String,
+ patientId: UuidId[Patient],
+ requestId: RecordRequestId,
+ recordId: LongId[MedicalRecord],
+ recordStatus: MedicalRecord.Status,
+ documentId: LongId[Document],
+ documentType: String,
+ documentProviderType: String,
+ documentStartDate: LocalDateTime,
+ documentStatus: Document.Status)
+
+object RawPatientDocument {
+
+ implicit def toPhiString(x: RawPatientDocument): PhiString = {
+ import x._
+ phi"RawPatientDocument(disease=${Unsafe(disease)}, patientId=$patientId, requestId=$requestId, " +
+ phi"recordId=$recordId, recordStatus=$recordStatus, documentId=$documentId, " +
+ phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(documentProviderType)}, " +
+ phi"startDate=$documentStartDate, documentStatus=$documentStatus)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala
new file mode 100644
index 0000000..e0cf06b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala
@@ -0,0 +1,33 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+case class RawPatientLabel(patientId: UuidId[Patient],
+ labelId: LongId[Label],
+ label: String,
+ value: FuzzyValue,
+ evidenceId: LongId[ExtractedData],
+ evidenceText: String,
+ disease: String,
+ documentId: LongId[Document],
+ requestId: RecordRequestId,
+ documentType: String,
+ providerType: String,
+ startDate: LocalDateTime,
+ endDate: Option[LocalDateTime])
+
+object RawPatientLabel {
+
+ implicit def toPhiString(x: RawPatientLabel): PhiString = {
+ import x._
+ phi"RawPatientLabel(patientId=$patientId, labelId=$labelId, label=${Unsafe(label)}, value=$value," +
+ phi"evidenceId=${Unsafe(evidenceId)}, " +
+ phi"evidenceText=${Unsafe(evidenceText)}, documentId=$documentId, requestId=${Unsafe(requestId)}, " +
+ phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(providerType)}, " +
+ phi"startDate=$startDate, endDate=$endDate)"
+ }
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala
new file mode 100644
index 0000000..e3b323f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+
+case class RawTrialLabel(nctId: StringId[Trial],
+ trialId: UuidId[Trial],
+ condition: String,
+ lastReviewed: LocalDateTime,
+ armName: String,
+ armId: LongId[Arm],
+ labelId: LongId[Label],
+ label: String,
+ value: Option[Boolean],
+ criterionId: LongId[Criterion],
+ criteria: String,
+ criterionArmId: LongId[Arm],
+ isCompound: Boolean,
+ isDefining: Boolean)
+
+object RawTrialLabel {
+
+ implicit def toPhiString(x: RawTrialLabel): PhiString = {
+ import x._
+ phi"RawTrialLabel(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, lastReviewed=$lastReviewed, " +
+ phi"armId=$armId, armName=${Unsafe(armName)}, labelId=$labelId, label=${Unsafe(label)}, value=$value, " +
+ phi"criterionId=$criterionId, criteria=${Unsafe(criteria)}, criterionArmId=$criterionArmId, " +
+ phi"isCompound=$isCompound, isDefining=$isDefining)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RecordRequestId.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RecordRequestId.scala
new file mode 100644
index 0000000..5df87c2
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RecordRequestId.scala
@@ -0,0 +1,15 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.util.UUID
+import xyz.driver.pdsuicommon.logging._
+
+final case class RecordRequestId(id: UUID) {
+ override def toString: String = id.toString
+}
+
+object RecordRequestId {
+
+ def apply() = new RecordRequestId(UUID.randomUUID())
+
+ implicit def toPhiString(x: RecordRequestId): PhiString = phi"${x.id}"
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala
new file mode 100644
index 0000000..2477c22
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala
@@ -0,0 +1,102 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.nio.file.Path
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId, User, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+import xyz.driver.pdsuidomain.entities.Trial.{Condition, Status}
+
+
+final case class StudyDesign(id: LongId[StudyDesign], name: String)
+
+object StudyDesign {
+ implicit def toPhiString(x: StudyDesign): PhiString = {
+ import x._
+ phi"StudyDesign(id=$id, category=${Unsafe(name)})"
+ }
+}
+
+object Trial {
+
+ sealed trait Status {
+ def oneOf(xs: Status*): Boolean = xs.contains(this)
+ def oneOf(xs: Set[Status]): Boolean = xs.contains(this)
+ }
+
+ object Status {
+ case object New extends Status
+ case object ReviewSummary extends Status
+ case object Summarized extends Status
+ case object PendingUpdate extends Status
+ case object Update extends Status
+ case object ReviewCriteria extends Status
+ case object Done extends Status
+ case object Flagged extends Status
+ case object Archived extends Status
+
+ val All = Set[Status](
+ New, ReviewSummary, Summarized, PendingUpdate, Update, ReviewCriteria, Done, Flagged, Archived
+ )
+
+ val AllPrevious = Set[Status](New, ReviewSummary, Summarized, ReviewCriteria)
+
+ implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ case class PdfSource(path: Path) extends AnyVal
+
+ implicit def toPhiString(x: Trial): PhiString = {
+ import x._
+ phi"Trial(id=$id, externalId=$externalId, status=$status, previousStatus=$previousStatus, " +
+ phi"assignee=$assignee, previousAssignee=$previousAssignee, isSummaryReviewed=$isSummaryReviewed, " +
+ phi"isCriteriaReviewed=$isCriteriaReviewed)"
+ }
+
+ case class Locations(locations: List[String])
+
+ sealed trait Condition
+
+ object Condition {
+
+ case object Breast extends Condition
+ case object Lung extends Condition
+ case object Prostate extends Condition
+
+ val All = Set(Breast, Lung, Prostate)
+ }
+}
+
+final case class Trial(id: StringId[Trial],
+ externalId: UuidId[Trial],
+ status: Status,
+ assignee: Option[LongId[User]],
+ previousStatus: Option[Status],
+ previousAssignee: Option[LongId[User]],
+ lastUpdate: LocalDateTime,
+ condition: Condition,
+ phase: String,
+ hypothesisId: Option[UuidId[Hypothesis]],
+ studyDesignId: Option[LongId[StudyDesign]],
+ originalStudyDesign: Option[String],
+ isPartner: Boolean,
+ overview: Option[String],
+ overviewTemplate: String,
+ isUpdated: Boolean,
+ title: String,
+ originalTitle: String,
+ isSummaryReviewed: Boolean,
+ isCriteriaReviewed: Boolean,
+ eligibilityCriteriaChecksum: String,
+ briefSummaryChecksum: String,
+ detailedDescriptionChecksum: String,
+ armDescriptionChecksum: String) {
+
+ import Trial.Status._
+
+ if (previousStatus.nonEmpty) {
+ assert(AllPrevious.contains(previousStatus.get),
+ s"Previous status has invalid value: ${previousStatus.get}")
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala
new file mode 100644
index 0000000..7d5de79
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala
@@ -0,0 +1,30 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Label, RawPatientLabel}
+
+case class ExportPatientLabel(id: LongId[Label],
+ name: String,
+ evidences: List[ExportPatientLabelEvidence])
+
+object ExportPatientLabel extends PhiLogging {
+
+ implicit def toPhiString(x: ExportPatientLabel): PhiString = {
+ import x._
+ phi"ExportPatientLabel(id=$id, evidences=$evidences)"
+ }
+
+ def fromRaw(labelId: LongId[Label], rawPatientLabels: List[RawPatientLabel]): ExportPatientLabel = {
+ val firstLabel = rawPatientLabels.headOption
+ if (firstLabel.isEmpty) {
+ logger.warn(phi"rawPatientLabels is empty, labelId: $labelId")
+ }
+
+ ExportPatientLabel(
+ id = labelId,
+ name = firstLabel.map(_.label).getOrElse(""),
+ evidences = rawPatientLabels.map(ExportPatientLabelEvidence.fromRaw)
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala
new file mode 100644
index 0000000..ff0fb6c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{ExtractedData, RawPatientLabel}
+
+case class ExportPatientLabelEvidence(id: LongId[ExtractedData],
+ value: FuzzyValue,
+ evidenceText: String,
+ document: ExportPatientLabelEvidenceDocument)
+
+object ExportPatientLabelEvidence {
+
+ implicit def toPhiString(x: ExportPatientLabelEvidence): PhiString = {
+ import x._
+ phi"ExportPatientLabelEvidence(id=${Unsafe(id)}, value=$value, " +
+ phi"evidenceText=${Unsafe(evidenceText)}, document=$document)"
+ }
+
+ def fromRaw(x: RawPatientLabel) = ExportPatientLabelEvidence(
+ id = x.evidenceId,
+ value = x.value,
+ evidenceText = x.evidenceText,
+ document = ExportPatientLabelEvidenceDocument(
+ x.documentId,
+ x.requestId,
+ x.documentType,
+ x.providerType,
+ x.startDate
+ )
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala
new file mode 100644
index 0000000..d696569
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala
@@ -0,0 +1,30 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Document, RawPatientLabel, RecordRequestId}
+
+case class ExportPatientLabelEvidenceDocument(documentId: LongId[Document],
+ requestId: RecordRequestId,
+ documentType: String,
+ providerType: String,
+ date: LocalDateTime)
+
+object ExportPatientLabelEvidenceDocument extends PhiLogging {
+
+ implicit def toPhiString(x: ExportPatientLabelEvidenceDocument): PhiString = {
+ import x._
+ phi"ExportPatientLabelEvidenceDocument(documentId=$documentId, requestId=$requestId, " +
+ phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(providerType)}, date=$date)"
+ }
+
+ def fromRaw(x: RawPatientLabel) = ExportPatientLabelEvidenceDocument(
+ documentId = x.documentId,
+ requestId = x.requestId,
+ documentType = x.documentType,
+ providerType = x.providerType,
+ date = x.startDate
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala
new file mode 100644
index 0000000..418f20b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala
@@ -0,0 +1,26 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain.UuidId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Patient, RawPatientLabel}
+
+import scala.collection.breakOut
+
+case class ExportPatientWithLabels(patientId: UuidId[Patient], labelVersion: Long, labels: List[ExportPatientLabel])
+
+object ExportPatientWithLabels {
+
+ implicit def toPhiString(x: ExportPatientWithLabels): PhiString = {
+ import x._
+ phi"ExportPatientWithLabels(patientId=$patientId, version=${Unsafe(labelVersion)}, labels=$labels)"
+ }
+
+ def fromRaw(patientId: UuidId[Patient],
+ rawPatientRefs: List[RawPatientLabel]) = ExportPatientWithLabels(
+ patientId = patientId,
+ labelVersion = 1L, // TODO It is needed to replace this mock label version.
+ labels = rawPatientRefs
+ .groupBy(_.labelId)
+ .map(Function.tupled(ExportPatientLabel.fromRaw))(breakOut)
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala
new file mode 100644
index 0000000..a3a0e90
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala
@@ -0,0 +1,27 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{StringId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Trial
+
+case class ExportTrial(nctId: StringId[Trial],
+ trialId: UuidId[Trial],
+ condition: Trial.Condition,
+ lastReviewed: LocalDateTime)
+
+object ExportTrial {
+
+ implicit def toPhiString(x: ExportTrial): PhiString = {
+ import x._
+ phi"ExportTrial(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, lastReviewed=$lastReviewed)"
+ }
+
+ def fromDomain(x: Trial) = ExportTrial(
+ nctId = x.id,
+ trialId = x.externalId,
+ condition = x.condition,
+ lastReviewed = x.lastUpdate
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala
new file mode 100644
index 0000000..abdade4
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala
@@ -0,0 +1,15 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Arm
+
+case class ExportTrialArm(armId: LongId[Arm], armName: String)
+
+object ExportTrialArm {
+
+ implicit def toPhiString(x: ExportTrialArm): PhiString = {
+ import x._
+ phi"ExportTrialArm(armId=$armId, armName=${Unsafe(armName)})"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala
new file mode 100644
index 0000000..1dccaa5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Arm, Criterion, Label, RawTrialLabel}
+
+case class ExportTrialLabelCriterion(criterionId: LongId[Criterion],
+ value: Option[Boolean],
+ labelId: LongId[Label],
+ armIds: Set[LongId[Arm]],
+ criteria: String,
+ isCompound: Boolean,
+ isDefining: Boolean)
+
+object ExportTrialLabelCriterion {
+
+ implicit def toPhiString(x: ExportTrialLabelCriterion): PhiString = {
+ import x._
+ phi"TrialLabelCriterion(criterionId=$criterionId, value=$value, labelId=$labelId, " +
+ phi"criteria=${Unsafe(criteria)}, isCompound=$isCompound, isDefining=$isDefining)"
+ }
+
+ def fromRaw(x: RawTrialLabel, armIds: Set[LongId[Arm]]) = ExportTrialLabelCriterion(
+ criterionId = x.criterionId,
+ value = x.value,
+ labelId = x.labelId,
+ armIds = armIds,
+ criteria = x.criteria,
+ isCompound = x.isCompound,
+ isDefining = x.isDefining
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala
new file mode 100644
index 0000000..251f6fb
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala
@@ -0,0 +1,51 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain.{StringId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{RawTrialLabel, Trial}
+
+import scala.collection.breakOut
+
+case class ExportTrialWithLabels(nctId: StringId[Trial],
+ trialId: UuidId[Trial],
+ condition: String,
+ lastReviewed: LocalDateTime,
+ labelVersion: Long,
+ arms: List[ExportTrialArm],
+ criteria: List[ExportTrialLabelCriterion])
+
+object ExportTrialWithLabels {
+
+ implicit def toPhiString(x: ExportTrialWithLabels): PhiString = {
+ import x._
+ phi"TrialWithLabels(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, " +
+ phi"lastReviewed=$lastReviewed, labelVersion=${Unsafe(labelVersion)}, arms=$arms, criteria=$criteria)"
+ }
+
+ def fromRaw(rawData: List[RawTrialLabel]): ExportTrialWithLabels = {
+ val trials: Set[StringId[Trial]] = rawData.map(_.nctId)(breakOut)
+
+ assert(trials.size == 1, "There are more than one trials in the rawData")
+ val trial = rawData.head
+
+ ExportTrialWithLabels(
+ nctId = trial.nctId,
+ trialId = trial.trialId,
+ condition = trial.condition,
+ lastReviewed = trial.lastReviewed,
+ labelVersion = 1, // TODO It is needed to replace this mock label version.
+ arms = rawData.groupBy(_.armId).map { case (armId, rawTrials) =>
+ ExportTrialArm(armId, rawTrials.head.armName)
+ }(breakOut),
+ criteria = rawData.groupBy { x =>
+ (x.criterionId, x.labelId)
+ }.map {
+ case (_, rawTrialLabels) =>
+ val armIds = rawTrialLabels.map(_.criterionArmId).toSet
+ ExportTrialLabelCriterion.fromRaw(rawTrialLabels.head, armIds)
+ }(breakOut)
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala
new file mode 100644
index 0000000..31ccff7
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala
@@ -0,0 +1,28 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.Sorting
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.CategoryWithLabels
+
+import scala.concurrent.Future
+
+object CategoryService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[CategoryWithLabels], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait CategoryService extends PhiLogging {
+
+ import CategoryService._
+
+ def getAll(sorting: Option[Sorting] = None)(implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala
new file mode 100644
index 0000000..eaffb2a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala
@@ -0,0 +1,60 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AnonymousRequestContext
+import xyz.driver.pdsuicommon.db.SearchFilterExpr
+import xyz.driver.pdsuicommon.domain.{StringId, UuidId}
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Patient, Trial}
+import xyz.driver.pdsuidomain.entities.export.patient.ExportPatientWithLabels
+import xyz.driver.pdsuidomain.entities.export.trial.{ExportTrial, ExportTrialWithLabels}
+
+import scala.concurrent.Future
+
+object ExportService {
+
+ sealed trait GetPatientReply
+ object GetPatientReply {
+ type Error = GetPatientReply with DomainError
+
+ case class Entity(x: ExportPatientWithLabels) extends GetPatientReply
+
+ case object NotFoundError extends GetPatientReply with DomainError.NotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+ }
+
+ sealed trait GetTrialListReply
+ object GetTrialListReply {
+ case class EntityList(xs: Seq[ExportTrial], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetTrialListReply
+ }
+
+ sealed trait GetTrialReply
+ object GetTrialReply {
+ type Error = GetTrialReply with DomainError
+
+ case class Entity(x: ExportTrialWithLabels) extends GetTrialReply
+
+ case object NotFoundError extends GetTrialReply with DomainError.NotFoundError {
+ def userMessage: String = "Trial not found"
+ }
+ }
+}
+
+trait ExportService extends PhiLogging {
+
+ import ExportService._
+
+ def getPatient(id: UuidId[Patient])
+ (implicit requestContext: AnonymousRequestContext): Future[GetPatientReply]
+
+ def getTrialList(filter: SearchFilterExpr = SearchFilterExpr.Empty)
+ (implicit requestContext: AnonymousRequestContext): Future[GetTrialListReply]
+
+ def getTrial(trialId: StringId[Trial], condition: String)
+ (implicit requestContext: AnonymousRequestContext): Future[GetTrialReply]
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala
new file mode 100644
index 0000000..25291f1
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala
@@ -0,0 +1,29 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.Sorting
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging.PhiLogging
+import xyz.driver.pdsuidomain.entities.Label
+
+import scala.concurrent.Future
+
+object LabelService {
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Label], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait LabelService extends PhiLogging {
+ import LabelService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala
new file mode 100644
index 0000000..56140ce
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala
@@ -0,0 +1,100 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Message
+
+import scala.concurrent.Future
+
+object MessageService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Message not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+ case class Created(x: Message)
+ extends CreateReply
+ case object AuthorizationError
+ extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case class CommonError(userMessage: String)
+ extends CreateReply with DomainError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ type Error = GetByIdReply with DomainError
+ case class Entity(x: Message)
+ extends GetByIdReply
+ case object NotFoundError
+ extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError
+ case class CommonError(userMessage: String)
+ extends GetByIdReply with DomainError
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ type Error = GetListReply with DomainError
+ case class EntityList(xs: Seq[Message], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetListReply
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+ case class Updated(updated: Message)
+ extends UpdateReply
+ case object AuthorizationError
+ extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+
+ sealed trait DeleteReply
+ object DeleteReply {
+ type Error = DeleteReply with DomainError
+ case object Deleted extends DeleteReply
+ case object AuthorizationError
+ extends DeleteReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case object NotFoundError
+ extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError
+ case class CommonError(userMessage: String)
+ extends DeleteReply with DomainError
+ }
+
+}
+
+trait MessageService extends PhiLogging {
+
+ import MessageService._
+
+ def create(draftMessage: Message)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def getById(messageId: LongId[Message])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def update(origMessage: Message, draftMessage: Message)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(messageId: LongId[Message])
+ (implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala
new file mode 100644
index 0000000..350720e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala
@@ -0,0 +1,143 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, AuthenticatedRequestContext}
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.domain.{Email, LongId, User}
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+
+import scala.concurrent.Future
+
+object UserService {
+
+ trait DefaultCredentialsError {
+ def userMessage: String = "Incorrect email/password. Try again."
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "User not found"
+ }
+
+ sealed trait ActivateExecutorReply
+ object ActivateExecutorReply {
+ type Error = ActivateExecutorReply with DomainError
+ case class Entity(x: User) extends ActivateExecutorReply
+ case object NotFoundError
+ extends ActivateExecutorReply with DomainError.NotFoundError {
+ val userMessage = "Info about you is not found on the server"
+ }
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ type Error = GetByIdReply with DomainError
+ case class Entity(x: User) extends GetByIdReply
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case object NotFoundError
+ extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError
+ case class CommonError(userMessage: String)
+ extends GetByIdReply with DomainError
+ }
+
+ sealed trait GetByEmailReply
+ object GetByEmailReply {
+ case class Entity(x: User) extends GetByEmailReply
+ case object NotFoundError
+ extends GetByEmailReply with DefaultNotFoundError with DomainError.NotFoundError {
+ override def userMessage: String = "Incorrect email. Try again."
+ }
+ }
+
+ sealed trait GetByCredentialsReply
+ object GetByCredentialsReply {
+ case class Entity(x: User) extends GetByCredentialsReply
+ case object AuthenticationError
+ extends GetByCredentialsReply with DefaultCredentialsError with DomainError.AuthenticationError
+ case object NotFoundError
+ extends GetByCredentialsReply with DomainError.NotFoundError with DefaultNotFoundError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[User], totalFound: Int) extends GetListReply
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultNotFoundError
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+ case class Created(x: User) extends CreateReply
+ case object AuthorizationError
+ extends CreateReply with DefaultNotFoundError with DomainError.AuthorizationError
+ case class UserAlreadyExistsError(email: Email)
+ extends CreateReply with DomainError {
+ val userMessage = s"The user with this email already exists."
+ }
+ case class CommonError(userMessage: String)
+ extends CreateReply with DomainError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+ case class Updated(updated: User) extends UpdateReply
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+
+ sealed trait DeleteReply
+ object DeleteReply {
+ type Error = DeleteReply with DomainError
+ case object Deleted extends DeleteReply
+ case class AuthorizationError(user: User)
+ extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+ case object AssignedToRecordAndDocumentError
+ extends DeleteReply with DomainError {
+ val userMessage = "User is can not be deleted because he has record and document in work"
+ }
+ case object NotFoundError
+ extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
+ case class CommonError(userMessage: String)
+ extends DeleteReply with DomainError
+ }
+
+}
+
+trait UserService extends PhiLogging {
+
+ import UserService._
+
+ /**
+ * Utility method for getting request executor.
+ */
+ def activateExecutor(executorId: LongId[User])
+ (implicit requestContext: AnonymousRequestContext): Future[ActivateExecutorReply]
+
+ def getById(userId: LongId[User])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getByEmail(email: Email)
+ (implicit requestContext: AnonymousRequestContext): Future[GetByEmailReply]
+
+ def getByCredentials(email: Email, password: String)
+ (implicit requestContext: AnonymousRequestContext): Future[GetByCredentialsReply]
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def create(draftUser: User)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def update(origUser: User, draftUser: User)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(userId: LongId[User])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/storage/MedicalRecordDocumentStorage.scala b/src/main/scala/xyz/driver/pdsuidomain/storage/MedicalRecordDocumentStorage.scala
new file mode 100644
index 0000000..d50be2c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/storage/MedicalRecordDocumentStorage.scala
@@ -0,0 +1,27 @@
+package xyz.driver.pdsuidomain.storage
+
+import com.google.cloud.storage.StorageOptions
+import com.typesafe.scalalogging.StrictLogging
+import xyz.driver.pdsuidomain.entities.MedicalRecord.PdfSource
+
+import scala.concurrent.{ExecutionContext, Future, blocking}
+
+object MedicalRecordDocumentStorage extends StrictLogging {
+ private val storage = StorageOptions.getDefaultInstance.getService
+
+ def fetchPdf(bucket: String, path: String)
+ (implicit ec: ExecutionContext): Future[PdfSource] = {
+ logger.trace(s"fetchPdf(bucket=$bucket, path=$path)")
+ Future {
+ blocking {
+ Option(storage.get(bucket, path)) match {
+ case Some(blob) =>
+ PdfSource.Channel(() => blob.reader())
+ case None =>
+ logger.error(s"Failed to find the pdf file $path in bucket: $bucket")
+ PdfSource.Empty
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/storage/RequestStorage.scala b/src/main/scala/xyz/driver/pdsuidomain/storage/RequestStorage.scala
new file mode 100644
index 0000000..d9651ca
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/storage/RequestStorage.scala
@@ -0,0 +1,52 @@
+package xyz.driver.pdsuidomain.storage
+
+import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Arm, Patient}
+
+import scala.collection.concurrent.TrieMap
+
+object RequestStorage {
+ type Key = (UuidId[Patient], String)
+ type Value = Set[LongId[Arm]]
+}
+
+class RequestStorage extends PhiLogging {
+ import RequestStorage._
+
+ private val storage = new TrieMap[Key, Value]()
+
+ def put(patientId: UuidId[Patient],
+ disease: String,
+ ineligibleArms: Set[LongId[Arm]]): Unit = {
+ logger.debug(phi"put($patientId, ${Unsafe(disease)}, $ineligibleArms")
+ val key = (patientId, disease.toLowerCase)
+ get(patientId, disease.toLowerCase) match {
+ case Some(oldValue) =>
+ logger.trace(phi"Requested ineligible arms=$oldValue, replace it")
+ storage.replace(key, oldValue, ineligibleArms)
+ case None =>
+ logger.trace(phi"Put request into storage")
+ storage.put(key, ineligibleArms)
+ }
+ }
+
+ def get(patientId: UuidId[Patient], disease: String): Option[Value] = {
+ logger.debug(phi"get($patientId,${Unsafe(disease)}")
+ val key = (patientId, disease.toLowerCase)
+ storage.get(key)
+ }
+
+ def contains(patientId: UuidId[Patient],
+ disease: String,
+ value: Set[LongId[Arm]]): Boolean = {
+ logger.debug(phi"contains(key=($patientId,${Unsafe(disease)}),value=$value")
+ get(patientId, disease.toLowerCase).contains(value)
+ }
+
+ def remove(patientId: UuidId[Patient], disease: String): Unit = {
+ logger.debug(phi"remove($patientId,${Unsafe(disease)}")
+ val key = (patientId, disease.toLowerCase)
+ storage.remove(key)
+ }
+}