aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvlad <vlad@driver.xyz>2017-06-27 17:13:02 -0700
committervlad <vlad@driver.xyz>2017-06-27 17:13:02 -0700
commit5832f63b84d7388441d1200f2442dc1e9de0225c (patch)
tree32f63acdc920c14effc3d0d2822c05c125ad49e4
parent9dd50590d4c8f8b9442d7c21ddd1def9dd453d5e (diff)
downloadrest-query-5832f63b84d7388441d1200f2442dc1e9de0225c.tar.gz
rest-query-5832f63b84d7388441d1200f2442dc1e9de0225c.tar.bz2
rest-query-5832f63b84d7388441d1200f2442dc1e9de0225c.zip
All PDS UI domain models, API case classes, service traits and necessary utils moved to pdsui-commonv0.1.11
-rw-r--r--build.sbt3
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala (renamed from src/main/scala/xyz/driver/pdsuicommon/utils/Computation.scala)2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala24
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/Implicits.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/JsonSerializer.scala (renamed from src/main/scala/xyz/driver/pdsuicommon/utils/JsonSerializer.scala)2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala5
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala42
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala25
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/validation/Validators.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/validation/package.scala8
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala10
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala22
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala10
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/ScrapedTrial.scala105
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala28
-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.scala25
-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.scala56
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala22
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala23
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala40
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala41
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala56
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala71
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala114
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala24
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala38
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala44
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala80
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala26
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala41
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala44
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala23
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala49
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala36
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala22
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala59
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala84
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala9
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala9
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala44
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala24
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala18
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala46
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala38
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala47
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala25
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala40
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala72
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala37
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala57
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala47
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala34
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala11
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala44
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala63
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala83
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala33
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala133
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala131
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala157
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala26
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala116
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala85
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala29
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala137
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala126
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala137
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala105
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala70
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala124
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala105
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala54
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala132
99 files changed, 4088 insertions, 305 deletions
diff --git a/build.sbt b/build.sbt
index 90e1a2c..0baf2f0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -11,6 +11,9 @@ lazy val core = (project in file("."))
"com.typesafe" % "config" % "1.3.0",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.3",
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4",
+ "com.typesafe.play" %% "play" % "2.5.15",
+ "org.davidbild" %% "tristate-core" % "0.2.0",
+ "org.davidbild" %% "tristate-play" % "0.2.0" exclude ("com.typesafe.play", "play-json"),
"org.asynchttpclient" % "async-http-client" % "2.0.24",
"io.getquill" %% "quill-jdbc" % "1.2.1",
"io.github.cloudify" %% "spdf" % "1.4.0",
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/Computation.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala
index 9e6f3bd..ad458de 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/Computation.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala
@@ -1,4 +1,4 @@
-package xyz.driver.pdsuicommon.utils
+package xyz.driver.pdsuicommon.computation
import scala.concurrent.{ExecutionContext, Future}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala
new file mode 100644
index 0000000..c5800dc
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/FutureToComputationOps.scala
@@ -0,0 +1,24 @@
+package xyz.driver.pdsuicommon.computation
+
+import xyz.driver.pdsuicommon.error.DomainError
+
+import scala.concurrent.{ExecutionContext, Future}
+
+final class FutureToComputationOps[T](val self: Future[T]) extends AnyVal {
+
+ def handleDomainError[U, ER](f: PartialFunction[T, U])
+ (implicit unsuitableToErrorsResponse: DomainError => ER,
+ ec: ExecutionContext): Future[Either[ER, U]] = {
+ self.map {
+ case x if f.isDefinedAt(x) => Right(f(x))
+ case x: DomainError => Left(unsuitableToErrorsResponse(x))
+ case x => throw new RuntimeException(s"Can not process $x")
+ }
+ }
+
+ def toComputation[U, ER](f: PartialFunction[T, U])
+ (implicit unsuitableToErrorsResponse: DomainError => ER,
+ ec: ExecutionContext): Computation[ER, U] = {
+ Computation(handleDomainError(f))
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/Implicits.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/Implicits.scala
new file mode 100644
index 0000000..d5acc2d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/Implicits.scala
@@ -0,0 +1,15 @@
+package xyz.driver.pdsuicommon.computation
+
+import scala.concurrent.Future
+import scala.util.Try
+
+trait Implicits {
+
+ implicit def futureToFutureComputationOps[T](self: Future[T]): FutureToComputationOps[T] = {
+ new FutureToComputationOps[T](self)
+ }
+
+ implicit def tryToTryComputationOps[T](self: Try[T]): TryToComputationOps[T] = {
+ new TryToComputationOps[T](self)
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala
new file mode 100644
index 0000000..8282bc6
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/computation/TryToComputationOps.scala
@@ -0,0 +1,15 @@
+package xyz.driver.pdsuicommon.computation
+
+import scala.concurrent.ExecutionContext
+import scala.util.control.NonFatal
+import scala.util.{Failure, Success, Try}
+
+final class TryToComputationOps[T](val self: Try[T]) extends AnyVal {
+
+ def toComputation[ER](implicit exceptionToErrorResponse: Throwable => ER,
+ ec: ExecutionContext): Computation[ER, T] = self match {
+ case Success(x) => Computation.continue(x)
+ case Failure(NonFatal(e)) => Computation.abort(exceptionToErrorResponse(e))
+ case Failure(e) => Computation.fail(e)
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
index 4c6bf3f..4e98f40 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala
@@ -15,4 +15,16 @@ object FuzzyValue {
def fromBoolean(x: Boolean): FuzzyValue = if (x) Yes else No
implicit def toPhiString(x: FuzzyValue): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+
+ val fromString: PartialFunction[String, FuzzyValue] = {
+ case "Yes" => Yes
+ case "No" => No
+ case "Maybe" => Maybe
+ }
+
+ def valueToString(x: FuzzyValue): String = x match {
+ case Yes => "Yes"
+ case No => "No"
+ case Maybe => "Maybe"
+ }
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/JsonSerializer.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsonSerializer.scala
index 8c8fd4e..a53f1dd 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/JsonSerializer.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/JsonSerializer.scala
@@ -1,4 +1,4 @@
-package xyz.driver.pdsuicommon.utils
+package xyz.driver.pdsuicommon.json
import java.text.SimpleDateFormat
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala
new file mode 100644
index 0000000..21750b4
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/JsonValidationException.scala
@@ -0,0 +1,5 @@
+package xyz.driver.pdsuicommon.json
+
+import xyz.driver.pdsuicommon.validation.JsonValidationErrors
+
+class JsonValidationException(val errors: JsonValidationErrors) extends Exception
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
new file mode 100644
index 0000000..a6d3ee9
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
@@ -0,0 +1,42 @@
+package xyz.driver.pdsuicommon.json
+
+import java.net.URI
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain._
+
+object Serialization {
+
+ // @TODO Test and check all items in an array
+ private def seqJsonReads[T](implicit argFormat: Reads[T]): Reads[Seq[T]] = Reads {
+ case JsArray(xs) => JsSuccess(xs.map { x => argFormat.reads(x).get })
+ case x => JsError(s"Expected JsArray, but got $x")
+ }
+
+ private def seqJsonWrites[T](implicit argFormat: Writes[T]): Writes[Seq[T]] = Writes { xs =>
+ JsArray(xs.map(argFormat.writes))
+ }
+
+ implicit def seqJsonFormat[T](implicit f: Format[T]): Format[Seq[T]] = Format(seqJsonReads[T], seqJsonWrites[T])
+
+ private val uriJsonReads: Reads[URI] = Reads.StringReads.map(URI.create)
+ private val uriJsonWrites: Writes[URI] = Writes(uri => JsString(uri.toString))
+ implicit val uriJsonFormat: Format[URI] = Format(uriJsonReads, uriJsonWrites)
+
+ private def uuidIdJsonReads[T]: Reads[UuidId[T]] = Reads.uuidReads.map(x => UuidId[T](x))
+ private def uuidIdJsonWrites[T]: Writes[UuidId[T]] = Writes.UuidWrites.contramap(_.id)
+ implicit def uuidIdJsonFormat[T]: Format[UuidId[T]] = Format(uuidIdJsonReads, uuidIdJsonWrites)
+
+ private def longIdJsonReads[T]: Reads[LongId[T]] = Reads.LongReads.map(x => LongId[T](x))
+ private def longIdJsonWrites[T]: Writes[LongId[T]] = Writes.LongWrites.contramap(_.id)
+ implicit def longIdJsonFormat[T]: Format[LongId[T]] = Format(longIdJsonReads, longIdJsonWrites)
+
+ private val emailJsonReads: Reads[Email] = Reads.email.map(Email.apply)
+ private val emailJsonWrites: Writes[Email] = Writes(email => JsString(email.value))
+ implicit val emailJsonFormat: Format[Email] = Format(emailJsonReads, emailJsonWrites)
+
+ private val passwordHashJsonReads: Reads[PasswordHash] = Reads.StringReads.map(hash => PasswordHash(hash.getBytes("UTF-8")))
+ private val passwordHashJsonWrites: Writes[PasswordHash] = Writes(passwordHash => JsString(passwordHash.value.toString))
+ implicit val passwordHashJsonFormat: Format[PasswordHash] = Format(passwordHashJsonReads, passwordHashJsonWrites)
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala b/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala
new file mode 100644
index 0000000..115163c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/validation/AdditionalConstraints.scala
@@ -0,0 +1,25 @@
+package xyz.driver.pdsuicommon.validation
+
+import org.davidbild.tristate.Tristate
+import play.api.data.validation._
+
+object AdditionalConstraints {
+
+ val optionNonEmptyConstraint: Constraint[Option[Any]] = {
+ Constraint("option.nonEmpty") {
+ case Some(x) => Valid
+ case None => Invalid("is empty")
+ }
+ }
+
+ val tristateSpecifiedConstraint: Constraint[Tristate[Any]] = {
+ Constraint("tristate.specified") {
+ case Tristate.Unspecified => Invalid("unspecified")
+ case _ => Valid
+ }
+ }
+
+ val uuid: Constraint[String] = {
+ Constraints.pattern("""[\da-z]{8}-[\da-z]{4}-[\da-z]{4}-[\da-z]{4}-[\da-z]{12}""".r, "uuid", "invalid uuid")
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/validation/Validators.scala b/src/main/scala/xyz/driver/pdsuicommon/validation/Validators.scala
index fca726c..a41f87a 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/validation/Validators.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/validation/Validators.scala
@@ -1,7 +1,7 @@
package xyz.driver.pdsuicommon.validation
+import xyz.driver.pdsuicommon.json.JsonSerializer
import xyz.driver.pdsuicommon.logging._
-import xyz.driver.pdsuicommon.utils.JsonSerializer
import scala.util.control.NonFatal
diff --git a/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala b/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala
new file mode 100644
index 0000000..9a31a93
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/validation/package.scala
@@ -0,0 +1,8 @@
+package xyz.driver.pdsuicommon
+
+import play.api.data.validation.{ValidationError => PlayValidationError}
+import play.api.libs.json.JsPath
+
+package object validation {
+ type JsonValidationErrors = Seq[(JsPath, Seq[PlayValidationError])]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala
index 70b84ff..2190b8d 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Arm.scala
@@ -5,11 +5,11 @@ 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)
+final case class Arm(id: LongId[Arm],
+ name: String,
+ originalName: String,
+ trialId: StringId[Trial],
+ deleted: Option[LocalDateTime] = None)
object Arm {
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
index 19c0021..5af00bc 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
@@ -34,7 +34,7 @@ object DocumentType {
object Document {
- case class Meta(predicted: Option[Boolean], startPage: Double, endPage: Double) {
+ final case class Meta(predicted: Option[Boolean], startPage: Double, endPage: Double) {
/**
* Return a regular meta: this meta is considered as not predicted
@@ -121,20 +121,20 @@ object Document {
}
@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[LocalDate], // not null
- endDate: Option[LocalDate],
- lastUpdate: LocalDateTime) {
+final 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[LocalDate], // not null
+ endDate: Option[LocalDate],
+ lastUpdate: LocalDateTime) {
import Document.Status._
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
index bc80ce3..93262a4 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
@@ -50,17 +50,17 @@ object Patient {
}
}
-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) {
+final 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._
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala
index 146b1c3..23bb546 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientHypothesis.scala
@@ -11,8 +11,8 @@ object PatientHypothesis {
}
}
-case class PatientHypothesis(id: UuidId[PatientHypothesis],
- patientId: UuidId[Patient],
- hypothesisId: UuidId[Hypothesis],
- rationale: Option[String],
- matchedTrials: Long)
+final 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
index 63d38f8..633a347 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabel.scala
@@ -29,10 +29,10 @@ object PatientLabelEvidence {
}
}
-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]])
+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/RawPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala
index 052b2fa..e7956a8 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientLabel.scala
@@ -5,19 +5,19 @@ import java.time.LocalDate
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: LocalDate,
- endDate: Option[LocalDate])
+final 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: LocalDate,
+ endDate: Option[LocalDate])
object RawPatientLabel {
@@ -29,5 +29,4 @@ object RawPatientLabel {
phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(providerType)}, " +
phi"startDate=$startDate, endDate=$endDate)"
}
-
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/ScrapedTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/ScrapedTrial.scala
new file mode 100644
index 0000000..40e84c9
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/ScrapedTrial.scala
@@ -0,0 +1,105 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDateTime
+import java.util.UUID
+
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.domain.UuidId
+
+final case class ScrapedStudyDesign(value: String)
+
+final case class ScrapedOverall(affiliation: String,
+ status: String,
+ facilityName: Option[String],
+ firstName: Option[String],
+ lastName: Option[String],
+ phone: Option[String],
+ phoneExt: Option[String],
+ email: Option[String],
+ isBackup: Boolean)
+
+final case class ScrapedLocationContact(firstName: Option[String],
+ lastName: Option[String],
+ phone: Option[String],
+ phoneExt: Option[String],
+ email: Option[String],
+ isBackup: Boolean)
+
+final case class ScrapedLocation(id: UuidId[ScrapedLocation],
+ createdAt: LocalDateTime,
+ facilityName: Option[String],
+ city: Option[String],
+ state: Option[String],
+ zip: Option[String],
+ country: Option[String],
+ latitude: Option[Double],
+ longitude: Option[Double],
+ preferredName: Option[String],
+ partnershipStatus: Option[String],
+ lastReviewed: LocalDateTime,
+ contacts: Set[ScrapedLocationContact])
+
+final case class ScrapedInterventionType(value: String)
+
+final case class ScrapedIntervention(name: String,
+ kind: ScrapedInterventionType,
+ description: Option[String],
+ isSynonym: Boolean)
+
+object ScrapedIntervention {
+
+ implicit def toPhiString(x: ScrapedIntervention): PhiString = phi"ScrapedIntervention(${Unsafe(x.name)})"
+}
+
+final case class ScrapedArm(name: String,
+ kind: Option[String],
+ interventions: Set[ScrapedIntervention])
+
+object ScrapedArm {
+
+ implicit def toPhiString(x: ScrapedArm): PhiString = {
+ import x._
+ phi"ScrapedArm(name=${Unsafe(name)}, inverventions=$interventions)"
+ }
+}
+
+final case class ScrapedTrialChecksum(eligibilityCriteria: String,
+ briefSummary: String,
+ detailedDescription: String,
+ armDescription: String)
+
+object ScrapedTrialChecksum {
+
+ implicit def toPhiString(x: ScrapedTrialChecksum): PhiString = {
+ import x._
+ phi"ScrapedTrialChecksum(eligibilityCriteria=${Unsafe(eligibilityCriteria)}, briefSummary=${Unsafe(briefSummary)}, " +
+ phi"detailedDescription=${Unsafe(detailedDescription)}, armDescription=${Unsafe(armDescription)}"
+ }
+}
+
+object ScrapedTrial {
+
+ implicit def toPhiString(x: ScrapedTrial): PhiString = {
+ import x._
+ phi"ScrapedTrial(rawId=$rawId, nctId=${Unsafe(nctId)}, " +
+ phi"location.size=${Unsafe(locations.size)}, arms=$arms, checksum=$checksum)"
+ }
+}
+
+final case class ScrapedTrial(rawId: UuidId[ScrapedTrial],
+ createdAt: LocalDateTime,
+ disease: String,
+ nctId: String,
+ nctUuid: UUID,
+ title: Option[String],
+ startDate: Option[LocalDateTime],
+ phase: String,
+ studyDesign: Option[ScrapedStudyDesign],
+ overall: Set[ScrapedOverall],
+ locations: Set[ScrapedLocation],
+ // // see ClinicalTrialRaw
+ // trialHtml: String,
+ // eligibilityText: String,
+ lastReviewed: LocalDateTime,
+ arms: Set[ScrapedArm],
+ checksum: ScrapedTrialChecksum)
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
deleted file mode 100644
index 3b7a6ad..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-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
deleted file mode 100644
index ff0fb6c..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-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
deleted file mode 100644
index 978c4b8..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-package xyz.driver.pdsuidomain.entities.export.patient
-
-import java.time.LocalDate
-
-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: LocalDate)
-
-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
deleted file mode 100644
index 718255b..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-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
deleted file mode 100644
index a3a0e90..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-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
deleted file mode 100644
index abdade4..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-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
deleted file mode 100644
index 1dccaa5..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-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
deleted file mode 100644
index 2580e54..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala
+++ /dev/null
@@ -1,56 +0,0 @@
-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/formats/json/arm/ApiArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala
new file mode 100644
index 0000000..509ea35
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala
@@ -0,0 +1,22 @@
+package xyz.driver.pdsuidomain.formats.json.arm
+
+import xyz.driver.pdsuidomain.entities.Arm
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiArm(id: Long, name: String, trialId: String)
+
+object ApiArm {
+
+ implicit val format: Format[ApiArm] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "trialId").format[String]
+ ) (ApiArm.apply, unlift(ApiArm.unapply))
+
+ def fromDomain(arm: Arm): ApiArm = ApiArm(
+ id = arm.id.id,
+ name = arm.name,
+ trialId = arm.trialId.id
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala
new file mode 100644
index 0000000..5168e94
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala
@@ -0,0 +1,20 @@
+package xyz.driver.pdsuidomain.formats.json.arm
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuidomain.entities.Arm
+import play.api.libs.json.{Format, Json}
+
+final case class ApiCreateArm(name: String, trialId: String) {
+
+ def toDomain = Arm(
+ id = LongId(0),
+ name = name,
+ trialId = StringId(trialId),
+ originalName = name
+ )
+}
+
+object ApiCreateArm {
+
+ implicit val format: Format[ApiCreateArm] = Json.format[ApiCreateArm]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala
new file mode 100644
index 0000000..f85d7ff
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala
@@ -0,0 +1,14 @@
+package xyz.driver.pdsuidomain.formats.json.arm
+
+import xyz.driver.pdsuidomain.entities.Arm
+import play.api.libs.json.{Format, Json}
+
+final case class ApiPartialArm(name: String) {
+
+ def applyTo(arm: Arm): Arm = arm.copy(name = name)
+}
+
+object ApiPartialArm {
+
+ implicit val format: Format[ApiPartialArm] = Json.format
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala
new file mode 100644
index 0000000..fabdaa2
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala
@@ -0,0 +1,23 @@
+package xyz.driver.pdsuidomain.formats.json.category
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.CategoryWithLabels
+import xyz.driver.pdsuidomain.formats.json.label.ApiLabel
+
+final case class ApiCategory(id: Long, name: String, labels: List[ApiLabel])
+
+object ApiCategory {
+
+ implicit val format: Format[ApiCategory] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "labels").format[List[ApiLabel]]
+ ) (ApiCategory.apply, unlift(ApiCategory.unapply))
+
+ def fromDomain(categoryWithLabels: CategoryWithLabels) = ApiCategory(
+ id = categoryWithLabels.category.id.id,
+ name = categoryWithLabels.category.name,
+ labels = categoryWithLabels.labels.map(x => ApiLabel(x.id.id, x.name, x.categoryId.id))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala
new file mode 100644
index 0000000..0f3b76e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala
@@ -0,0 +1,40 @@
+package xyz.driver.pdsuidomain.formats.json.criterion
+
+import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel
+import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion
+
+final case class ApiCriterion(id: Long,
+ meta: Option[String],
+ arms: Seq[Long],
+ text: Option[String],
+ isCompound: Boolean,
+ labels: Seq[ApiCriterionLabel],
+ trialId: String)
+
+object ApiCriterion {
+
+ implicit val format: Format[ApiCriterion] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "meta").formatNullable(Format(Reads { x =>
+ JsSuccess(Json.stringify(x))
+ }, Writes[String](Json.parse))) and
+ (JsPath \ "arms").format(seqJsonFormat[Long]) and
+ (JsPath \ "text").formatNullable[String] and
+ (JsPath \ "isCompound").format[Boolean] and
+ (JsPath \ "labels").format(seqJsonFormat[ApiCriterionLabel]) and
+ (JsPath \ "trialId").format[String]
+ ) (ApiCriterion.apply, unlift(ApiCriterion.unapply))
+
+ def fromDomain(richCriterion: RichCriterion) = ApiCriterion(
+ id = richCriterion.criterion.id.id,
+ meta = Option(richCriterion.criterion.meta),
+ arms = richCriterion.armIds.map(_.id),
+ text = richCriterion.criterion.text,
+ isCompound = richCriterion.criterion.isCompound,
+ labels = richCriterion.labels.map(ApiCriterionLabel.fromDomain),
+ trialId = richCriterion.criterion.trialId.id
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala
new file mode 100644
index 0000000..85c91d5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala
@@ -0,0 +1,41 @@
+package xyz.driver.pdsuidomain.formats.json.criterion
+
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat
+import xyz.driver.pdsuidomain.entities._
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel
+import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion
+
+final case class ApiNewCriterion(meta: Option[String],
+ arms: Option[Seq[Long]],
+ text: Option[String],
+ labels: Seq[ApiCriterionLabel],
+ trialId: String) {
+
+ def toDomain = RichCriterion(
+ criterion = Criterion(
+ id = LongId(0L),
+ meta = meta.getOrElse(""),
+ trialId = StringId(trialId),
+ isCompound = false,
+ text = text
+ ),
+ armIds = arms.getOrElse(Seq.empty).map(LongId[Arm]),
+ labels = labels.map(_.toDomain(LongId(Long.MaxValue))) // A developer should specify right criterionId himself
+ )
+}
+
+object ApiNewCriterion {
+
+ implicit val format: Format[ApiNewCriterion] = (
+ (JsPath \ "meta").formatNullable(Format(Reads { x =>
+ JsSuccess(Json.stringify(x))
+ }, Writes[String](Json.parse))) and
+ (JsPath \ "arms").formatNullable(seqJsonFormat[Long]) and
+ (JsPath \ "text").formatNullable[String] and
+ (JsPath \ "labels").format(seqJsonFormat[ApiCriterionLabel]) and
+ (JsPath \ "trialId").format[String]
+ ) (ApiNewCriterion.apply, unlift(ApiNewCriterion.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala
new file mode 100644
index 0000000..c73c3ef
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala
@@ -0,0 +1,56 @@
+package xyz.driver.pdsuidomain.formats.json.criterion
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat
+import xyz.driver.pdsuidomain.entities.{Arm, Criterion}
+import org.davidbild.tristate._
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel
+import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion
+
+final case class ApiUpdateCriterion(meta: Tristate[String],
+ arms: Tristate[Seq[Long]],
+ text: Option[String],
+ isCompound: Option[Boolean],
+ labels: Tristate[Seq[ApiCriterionLabel]]) {
+
+ def applyTo(orig: RichCriterion): RichCriterion = RichCriterion(
+ criterion = applyTo(orig.criterion),
+ armIds = arms.cata(_.map(LongId[Arm]), Seq.empty, orig.armIds),
+ labels = labels.cata(_.map(_.toDomain(orig.criterion.id)), Seq.empty, orig.labels)
+ )
+
+ private def applyTo(orig: Criterion): Criterion = Criterion(
+ id = orig.id,
+ meta = meta.cata(identity, "{}", orig.meta),
+ text = text.orElse(orig.text),
+ isCompound = isCompound.getOrElse(orig.isCompound),
+ trialId = orig.trialId
+ )
+}
+
+object ApiUpdateCriterion {
+
+ private val reads: Reads[ApiUpdateCriterion] = (
+ (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map {
+ case Tristate.Present("{}") => Tristate.Absent
+ case x => x
+ } and
+ (JsPath \ "arms").readTristate(seqJsonFormat[Long]) and
+ (JsPath \ "text").readNullable[String] and
+ (JsPath \ "isCompound").readNullable[Boolean] and
+ (JsPath \ "labels").readTristate(seqJsonFormat[ApiCriterionLabel])
+ ) (ApiUpdateCriterion.apply _)
+
+ private val writes: Writes[ApiUpdateCriterion] = (
+ (JsPath \ "meta").writeTristate(Writes[String](Json.parse)) and
+ (JsPath \ "arms").writeTristate(seqJsonFormat[Long]) and
+ (JsPath \ "text").writeNullable[String] and
+ (JsPath \ "isCompound").writeNullable[Boolean] and
+ (JsPath \ "labels").writeTristate(seqJsonFormat[ApiCriterionLabel])
+ ) (unlift(ApiUpdateCriterion.unapply))
+
+ implicit val format: Format[ApiUpdateCriterion] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala
new file mode 100644
index 0000000..be9c65b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala
@@ -0,0 +1,71 @@
+package xyz.driver.pdsuidomain.formats.json.document
+
+import java.time.{LocalDate, ZoneId, ZonedDateTime}
+
+import xyz.driver.pdsuidomain.entities._
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.JsonSerializer
+
+final case class ApiDocument(id: Long,
+ recordId: Long,
+ physician: Option[String],
+ lastUpdate: Option[ZonedDateTime],
+ typeId: Option[Long],
+ startDate: Option[LocalDate],
+ endDate: Option[LocalDate],
+ provider: Option[String],
+ providerTypeId: Option[Long],
+ status: Option[String],
+ previousStatus: Option[String],
+ assignee: Option[Long],
+ previousAssignee: Option[Long],
+ meta: Option[String])
+
+object ApiDocument {
+
+ private val statusFormat = Format(
+ Reads.StringReads.filter(ValidationError("unknown status")) {
+ case x if DocumentUtils.statusFromString.isDefinedAt(x) => true
+ case _ => false
+ },
+ Writes.StringWrites
+ )
+
+ implicit val format: Format[ApiDocument] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "recordId").format[Long] and
+ (JsPath \ "physician").formatNullable[String] and
+ (JsPath \ "lastUpdate").formatNullable[ZonedDateTime] and
+ (JsPath \ "typeId").formatNullable[Long] and
+ (JsPath \ "startDate").formatNullable[LocalDate] and
+ (JsPath \ "endDate").formatNullable[LocalDate] and
+ (JsPath \ "provider").formatNullable[String] and
+ (JsPath \ "providerTypeId").formatNullable[Long] and
+ (JsPath \ "status").formatNullable(statusFormat) and
+ (JsPath \ "previousStatus").formatNullable(statusFormat) and
+ (JsPath \ "assignee").formatNullable[Long] and
+ (JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "meta").formatNullable(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse)))
+ ) (ApiDocument.apply, unlift(ApiDocument.unapply))
+
+ def fromDomain(document: Document): ApiDocument = {
+ ApiDocument(
+ id = document.id.id,
+ recordId = document.recordId.id,
+ physician = document.physician,
+ lastUpdate = Option(document.lastUpdate).map(ZonedDateTime.of(_, ZoneId.of("Z"))),
+ typeId = document.typeId.map(_.id),
+ startDate = document.startDate,
+ endDate = document.endDate,
+ provider = document.providerName,
+ providerTypeId = document.providerTypeId.map(_.id),
+ status = Option(DocumentUtils.statusToString(document.status)),
+ previousStatus = document.previousStatus.map(DocumentUtils.statusToString),
+ assignee = document.assignee.map(_.id),
+ previousAssignee = document.previousAssignee.map(_.id),
+ meta = document.meta.map(meta => JsonSerializer.serialize(meta.content))
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala
new file mode 100644
index 0000000..e00da20
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala
@@ -0,0 +1,20 @@
+package xyz.driver.pdsuidomain.formats.json.document
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.DocumentType
+
+final case class ApiDocumentType(id: Long, name: String)
+
+object ApiDocumentType {
+
+ implicit val format: Format[ApiDocumentType] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String]
+ ) (ApiDocumentType.apply, unlift(ApiDocumentType.unapply))
+
+ def fromDomain(documentType: DocumentType) = ApiDocumentType(
+ id = documentType.id.id,
+ name = documentType.name
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala
new file mode 100644
index 0000000..7682bb5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala
@@ -0,0 +1,114 @@
+package xyz.driver.pdsuidomain.formats.json.document
+
+import java.time.{LocalDate, LocalDateTime}
+
+import xyz.driver.pdsuicommon.domain.{LongId, TextJson}
+import xyz.driver.pdsuidomain.entities.Document.Meta
+import xyz.driver.pdsuidomain.entities._
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.data.validation._
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.{JsonSerializer, JsonValidationException}
+import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors}
+
+import scala.collection.breakOut
+import scala.util.Try
+
+final case class ApiPartialDocument(recordId: Option[Long],
+ physician: Option[String],
+ typeId: Tristate[Long],
+ startDate: Tristate[LocalDate],
+ endDate: Tristate[LocalDate],
+ provider: Tristate[String],
+ providerTypeId: Tristate[Long],
+ status: Option[String],
+ assignee: Tristate[Long],
+ meta: Tristate[String]) {
+
+ import xyz.driver.pdsuicommon.domain.User
+
+ def applyTo(orig: Document): Document = Document(
+ id = orig.id,
+ status = status.map(DocumentUtils.statusFromString).getOrElse(orig.status),
+ previousStatus = orig.previousStatus,
+ assignee = assignee.map(LongId[User]).cata(Some(_), None, orig.assignee),
+ previousAssignee = orig.previousAssignee,
+ recordId = recordId.map(LongId[MedicalRecord]).getOrElse(orig.recordId),
+ physician = physician.orElse(orig.physician),
+ typeId = typeId.map(LongId[DocumentType]).cata(Some(_), None, orig.typeId),
+ providerName = provider.cata(Some(_), None, orig.providerName),
+ providerTypeId = providerTypeId.map(LongId[ProviderType]).cata(Some(_), None, orig.providerTypeId),
+ meta = meta.cata(x => Some(TextJson(JsonSerializer.deserialize[Meta](x))), None, orig.meta),
+ startDate = startDate.cata(Some(_), None, orig.startDate),
+ endDate = endDate.cata(Some(_), None, orig.endDate),
+ lastUpdate = LocalDateTime.MIN // Should update internally in a business logic module
+ )
+
+ def toDomain: Try[Document] = Try {
+ val validation = Map(JsPath \ "recordId" -> AdditionalConstraints.optionNonEmptyConstraint(recordId))
+
+ val validationErrors: JsonValidationErrors = validation.collect({
+ case (fieldName, e: Invalid) => (fieldName, e.errors)
+ })(breakOut)
+
+ if (validationErrors.isEmpty) {
+ Document(
+ id = LongId(0),
+ recordId = recordId.map(LongId[MedicalRecord]).get,
+ status = Document.Status.New,
+ physician = physician,
+ typeId = typeId.map(LongId[DocumentType]).toOption,
+ startDate = startDate.toOption,
+ endDate = endDate.toOption,
+ providerName = provider.toOption,
+ providerTypeId = providerTypeId.map(LongId[ProviderType]).toOption,
+ meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).toOption,
+ previousStatus = None,
+ assignee = None,
+ previousAssignee = None,
+ lastUpdate = LocalDateTime.MIN
+ )
+ } else {
+ throw new JsonValidationException(validationErrors)
+ }
+ }
+}
+
+object ApiPartialDocument {
+
+ private val reads: Reads[ApiPartialDocument] = (
+ (JsPath \ "recordId").readNullable[Long] and
+ (JsPath \ "physician").readNullable[String] and
+ (JsPath \ "typeId").readTristate[Long] and
+ (JsPath \ "startDate").readTristate[LocalDate] and
+ (JsPath \ "endDate").readTristate[LocalDate] and
+ (JsPath \ "provider").readTristate[String] and
+ (JsPath \ "providerTypeId").readTristate[Long] and
+ (JsPath \ "status").readNullable[String](Reads.of[String].filter(ValidationError("unknown status"))({
+ case x if DocumentUtils.statusFromString.isDefinedAt(x) => true
+ case _ => false
+ })) and
+ (JsPath \ "assignee").readTristate[Long] and
+ (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map {
+ case Tristate.Present("{}") => Tristate.Absent
+ case x => x
+ }
+ ) (ApiPartialDocument.apply _)
+
+ private val writes: Writes[ApiPartialDocument] = (
+ (JsPath \ "recordId").writeNullable[Long] and
+ (JsPath \ "physician").writeNullable[String] and
+ (JsPath \ "typeId").writeTristate[Long] and
+ (JsPath \ "startDate").writeTristate[LocalDate] and
+ (JsPath \ "endDate").writeTristate[LocalDate] and
+ (JsPath \ "provider").writeTristate[String] and
+ (JsPath \ "providerTypeId").writeTristate[Long] and
+ (JsPath \ "status").writeNullable[String] and
+ (JsPath \ "assignee").writeTristate[Long] and
+ (JsPath \ "meta").writeTristate(Writes[String](Json.parse))
+ ) (unlift(ApiPartialDocument.unapply))
+
+ implicit val format: Format[ApiPartialDocument] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala
new file mode 100644
index 0000000..7b370ba
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala
@@ -0,0 +1,20 @@
+package xyz.driver.pdsuidomain.formats.json.document
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.ProviderType
+
+final case class ApiProviderType(id: Long, name: String)
+
+object ApiProviderType {
+
+ implicit val format: Format[ApiProviderType] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String]
+ ) (ApiProviderType.apply, unlift(ApiProviderType.unapply))
+
+ def fromDomain(providerType: ProviderType) = ApiProviderType(
+ id = providerType.id.id,
+ name = providerType.name
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala
new file mode 100644
index 0000000..87e449f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala
@@ -0,0 +1,24 @@
+package xyz.driver.pdsuidomain.formats.json.document
+
+import xyz.driver.pdsuidomain.entities.Document.Status
+
+object DocumentUtils {
+
+ val statusFromString: PartialFunction[String, Status] = {
+ case "New" => Status.New
+ case "Organized" => Status.Organized
+ case "Extracted" => Status.Extracted
+ case "Done" => Status.Done
+ case "Flagged" => Status.Flagged
+ case "Archived" => Status.Archived
+ }
+
+ def statusToString(x: Status): String = x match {
+ case Status.New => "New"
+ case Status.Organized => "Organized"
+ case Status.Extracted => "Extracted"
+ case Status.Done => "Done"
+ case Status.Flagged => "Flagged"
+ case Status.Archived => "Archived"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala
new file mode 100644
index 0000000..2bb4945
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala
@@ -0,0 +1,38 @@
+package xyz.driver.pdsuidomain.formats.json.evidence
+
+import java.time.LocalDate
+
+import xyz.driver.pdsuidomain.services.PatientLabelEvidenceService
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+
+final case class ApiPatientLabelEvidence(id: Long,
+ value: String,
+ evidenceText: String,
+ documentId: Option[Long],
+ evidenceId: Option[Long],
+ reportId: Option[String],
+ documentType: String,
+ date: LocalDate,
+ providerType: String)
+
+object ApiPatientLabelEvidence {
+
+ implicit val format: Format[ApiPatientLabelEvidence] = Json.format
+
+ def fromDomain(x: PatientLabelEvidenceService.Aggregated): ApiPatientLabelEvidence = {
+ import x._
+
+ ApiPatientLabelEvidence(
+ id = evidence.id.id,
+ value = FuzzyValue.valueToString(evidence.value),
+ evidenceText = evidence.evidenceText,
+ documentId = evidence.documentId.map(_.id),
+ evidenceId = evidence.evidenceId.map(_.id),
+ reportId = evidence.reportId.map(_.toString),
+ documentType = documentType,
+ date = date,
+ providerType = providerType
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala
new file mode 100644
index 0000000..dad7a1e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala
@@ -0,0 +1,44 @@
+package xyz.driver.pdsuidomain.formats.json.extracteddata
+
+import xyz.driver.pdsuidomain.formats.json.label.ApiExtractedDataLabel
+import play.api.libs.json._
+import play.api.data.validation._
+import play.api.libs.functional.syntax._
+import xyz.driver.pdsuicommon.json.JsonSerializer
+import xyz.driver.pdsuidomain.services.ExtractedDataService.RichExtractedData
+
+// The specification: https://driverinc.atlassian.net/wiki/pages/viewpage.action?pageId=33423387
+// Note, that there is "Extracted data object or Temporary extracted data object" in specification
+// ApiExtractedData represents both types
+final case class ApiExtractedData(id: Long,
+ documentId: Long,
+ keywordId: Option[Long],
+ evidence: Option[String],
+ meta: Option[String],
+ // An empty list and no-existent list are different cases
+ labels: Option[List[ApiExtractedDataLabel]])
+
+object ApiExtractedData {
+
+ implicit val format: Format[ApiExtractedData] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "documentId").format[Long] and
+ (JsPath \ "keywordId").formatNullable[Long] and
+ (JsPath \ "evidence").formatNullable[String] and
+ (JsPath \ "meta").formatNullable[String] and
+ (JsPath \ "labels").formatNullable[List[ApiExtractedDataLabel]](Format(
+ Reads.of[List[ApiExtractedDataLabel]].filter(ValidationError("empty labels"))({
+ case x if x.nonEmpty => true
+ case _ => false
+ }), Writes.of[List[ApiExtractedDataLabel]]))
+ ) (ApiExtractedData.apply, unlift(ApiExtractedData.unapply))
+
+ def fromDomain(extractedDataWithLabels: RichExtractedData) = ApiExtractedData(
+ id = extractedDataWithLabels.extractedData.id.id,
+ documentId = extractedDataWithLabels.extractedData.documentId.id,
+ keywordId = extractedDataWithLabels.extractedData.keywordId.map(_.id),
+ evidence = extractedDataWithLabels.extractedData.evidenceText,
+ meta = extractedDataWithLabels.extractedData.meta.map(x => JsonSerializer.serialize(x.content)),
+ labels = Option(extractedDataWithLabels.labels.map(ApiExtractedDataLabel.fromDomain))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala
new file mode 100644
index 0000000..69b5627
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala
@@ -0,0 +1,80 @@
+package xyz.driver.pdsuidomain.formats.json.extracteddata
+
+import xyz.driver.pdsuicommon.domain.{LongId, TextJson}
+import xyz.driver.pdsuidomain.entities.ExtractedData.Meta
+import xyz.driver.pdsuidomain.entities._
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.data.validation._
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.{JsonSerializer, JsonValidationException}
+import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors}
+import xyz.driver.pdsuidomain.formats.json.label.ApiExtractedDataLabel
+import xyz.driver.pdsuidomain.services.ExtractedDataService.RichExtractedData
+
+import scala.collection._
+
+final case class ApiPartialExtractedData(documentId: Option[Long],
+ keywordId: Option[Long],
+ evidence: Tristate[String],
+ meta: Tristate[String],
+ labels: Tristate[List[ApiExtractedDataLabel]]) {
+
+ def applyTo(orig: RichExtractedData): RichExtractedData = RichExtractedData(
+ extractedData = applyTo(orig.extractedData),
+ labels = labels.cata(_.map(_.toDomain(orig.extractedData.id)), List.empty, orig.labels)
+ )
+
+ private def applyTo(orig: ExtractedData): ExtractedData = ExtractedData(
+ id = orig.id,
+ documentId = orig.documentId,
+ keywordId = keywordId.map(LongId[Keyword]).orElse(orig.keywordId),
+ evidenceText = evidence.cata(Some(_), None, orig.evidenceText),
+ meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).cata(Some(_), None, orig.meta)
+ )
+
+ def toDomain: RichExtractedData = {
+ val validation = Map(
+ JsPath \ "documentId" -> AdditionalConstraints.optionNonEmptyConstraint(documentId)
+ )
+
+ val validationErrors: JsonValidationErrors = validation.collect({
+ case (fieldName, e: Invalid) => (fieldName, e.errors)
+ })(breakOut)
+
+ if (validationErrors.isEmpty) {
+ val extractedData = ExtractedData(
+ documentId = documentId.map(LongId[Document]).get,
+ keywordId = keywordId.map(LongId[Keyword]),
+ evidenceText = evidence.toOption,
+ meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).toOption
+ )
+ val labelList = labels.map(_.map(_.toDomain()))
+ RichExtractedData(extractedData, labelList.getOrElse(List.empty))
+ } else {
+ throw new JsonValidationException(validationErrors)
+ }
+ }
+}
+
+object ApiPartialExtractedData {
+
+ private val reads: Reads[ApiPartialExtractedData] = (
+ (JsPath \ "documentId").readNullable[Long] and
+ (JsPath \ "keywordId").readNullable[Long] and
+ (JsPath \ "evidence").readTristate[String] and
+ (JsPath \ "meta").readTristate[String] and
+ (JsPath \ "labels").readTristate[List[ApiExtractedDataLabel]]
+ ) (ApiPartialExtractedData.apply _)
+
+ private val writes: Writes[ApiPartialExtractedData] = (
+ (JsPath \ "documentId").writeNullable[Long] and
+ (JsPath \ "keywordId").writeNullable[Long] and
+ (JsPath \ "evidence").writeTristate[String] and
+ (JsPath \ "meta").writeTristate[String] and
+ (JsPath \ "labels").writeTristate[List[ApiExtractedDataLabel]]
+ ) (unlift(ApiPartialExtractedData.unapply))
+
+ implicit val format: Format[ApiPartialExtractedData] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala
new file mode 100644
index 0000000..0d6763c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala
@@ -0,0 +1,26 @@
+package xyz.driver.pdsuidomain.formats.json.hypothesis
+
+import java.util.UUID
+
+import xyz.driver.pdsuidomain.entities.Hypothesis
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+
+final case class ApiHypothesis(id: UUID, name: String, treatmentType: String, description: String)
+
+object ApiHypothesis {
+
+ implicit val format: Format[ApiHypothesis] = (
+ (JsPath \ "id").format[UUID] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "treatmentType").format[String] and
+ (JsPath \ "description").format[String]
+ ) (ApiHypothesis.apply, unlift(ApiHypothesis.unapply))
+
+ def fromDomain(hypothesis: Hypothesis) = ApiHypothesis(
+ id = hypothesis.id.id,
+ name = hypothesis.name,
+ treatmentType = hypothesis.treatmentType,
+ description = hypothesis.description
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala
new file mode 100644
index 0000000..37a9758
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala
@@ -0,0 +1,41 @@
+package xyz.driver.pdsuidomain.formats.json.intervention
+
+import xyz.driver.pdsuidomain.entities.InterventionWithArms
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+
+final case class ApiIntervention(id: Long,
+ name: String,
+ typeId: Option[Long],
+ description: String,
+ isActive: Boolean,
+ arms: List[Long],
+ trialId: String)
+
+object ApiIntervention {
+
+ implicit val format: Format[ApiIntervention] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "typeId").formatNullable[Long] and
+ (JsPath \ "description").format[String] and
+ (JsPath \ "isActive").format[Boolean] and
+ (JsPath \ "arms").format[List[Long]] and
+ (JsPath \ "trialId").format[String]
+ ) (ApiIntervention.apply, unlift(ApiIntervention.unapply))
+
+ def fromDomain(interventionWithArms: InterventionWithArms): ApiIntervention = {
+ import interventionWithArms.intervention
+ import interventionWithArms.arms
+
+ ApiIntervention(
+ id = intervention.id.id,
+ name = intervention.name,
+ typeId = intervention.typeId.map(_.id),
+ description = intervention.description,
+ isActive = intervention.isActive,
+ arms = arms.map(_.armId.id),
+ trialId = intervention.trialId.id
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala
new file mode 100644
index 0000000..ca444eb
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala
@@ -0,0 +1,20 @@
+package xyz.driver.pdsuidomain.formats.json.intervention
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.InterventionType
+
+final case class ApiInterventionType(id: Long, name: String)
+
+object ApiInterventionType {
+
+ implicit val format: Format[ApiInterventionType] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String]
+ ) (ApiInterventionType.apply, unlift(ApiInterventionType.unapply))
+
+ def fromDomain(interventionType: InterventionType) = ApiInterventionType(
+ id = interventionType.id.id,
+ name = interventionType.name
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala
new file mode 100644
index 0000000..416237a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala
@@ -0,0 +1,44 @@
+package xyz.driver.pdsuidomain.formats.json.intervention
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuidomain.entities.{InterventionArm, InterventionWithArms}
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiPartialIntervention(typeId: Option[Long],
+ description: Option[String],
+ isActive: Option[Boolean],
+ arms: Option[List[Long]]) {
+
+ def applyTo(orig: InterventionWithArms): InterventionWithArms = {
+ val origIntervention = orig.intervention
+ val draftArmList = arms.map(_.map(x => InterventionArm(LongId(x), orig.intervention.id)))
+ orig.copy(
+ intervention = origIntervention.copy(
+ typeId = typeId.map(LongId(_)).orElse(origIntervention.typeId),
+ description = description.getOrElse(origIntervention.description),
+ isActive = isActive.getOrElse(origIntervention.isActive)
+ ),
+ arms = draftArmList.getOrElse(orig.arms)
+ )
+ }
+}
+
+object ApiPartialIntervention {
+
+ private val reads: Reads[ApiPartialIntervention] = (
+ (JsPath \ "typeId").readNullable[Long] and
+ (JsPath \ "description").readNullable[String] and
+ (JsPath \ "isActive").readNullable[Boolean] and
+ (JsPath \ "arms").readNullable[List[Long]]
+ ) (ApiPartialIntervention.apply _)
+
+ private val writes: Writes[ApiPartialIntervention] = (
+ (JsPath \ "typeId").writeNullable[Long] and
+ (JsPath \ "description").writeNullable[String] and
+ (JsPath \ "isActive").writeNullable[Boolean] and
+ (JsPath \ "arms").writeNullable[List[Long]]
+ ) (unlift(ApiPartialIntervention.unapply))
+
+ implicit val format: Format[ApiPartialIntervention] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala
new file mode 100644
index 0000000..afd012d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala
@@ -0,0 +1,23 @@
+package xyz.driver.pdsuidomain.formats.json.keyword
+
+import xyz.driver.pdsuidomain.entities.KeywordWithLabels
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.formats.json.label.ApiLabel
+
+final case class ApiKeyword(id: Long, keyword: String, labels: List[ApiLabel])
+
+object ApiKeyword {
+
+ implicit val format: Format[ApiKeyword] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "keyword").format[String] and
+ (JsPath \ "labels").format[List[ApiLabel]]
+ ) (ApiKeyword.apply, unlift(ApiKeyword.unapply))
+
+ def fromDomain(keywordWithLabels: KeywordWithLabels) = ApiKeyword(
+ id = keywordWithLabels.keyword.id.id,
+ keyword = keywordWithLabels.keyword.keyword,
+ labels = keywordWithLabels.labels.map(x => ApiLabel(x.id.id, x.name, x.categoryId.id))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala
new file mode 100644
index 0000000..2788bf2
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala
@@ -0,0 +1,49 @@
+package xyz.driver.pdsuidomain.formats.json.label
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId}
+import xyz.driver.pdsuidomain.entities.{Category, Criterion, CriterionLabel, Label}
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+/**
+ * @param value Yes|No
+ */
+final case class ApiCriterionLabel(labelId: Option[Long],
+ categoryId: Option[Long],
+ value: Option[String],
+ isDefining: Boolean) {
+
+ def toDomain(criterionId: LongId[Criterion]) = CriterionLabel(
+ id = LongId(0L),
+ labelId = labelId.map(LongId[Label]),
+ criterionId = criterionId,
+ categoryId = categoryId.map(LongId[Category]),
+ value = value.map {
+ case "Yes" => true
+ case "No" => false
+ },
+ isDefining = isDefining
+ )
+}
+
+object ApiCriterionLabel {
+
+ def fromDomain(x: CriterionLabel) = ApiCriterionLabel(
+ labelId = x.labelId.map(_.id),
+ categoryId = x.categoryId.map(_.id),
+ value = x.value.map { x =>
+ FuzzyValue.valueToString(FuzzyValue.fromBoolean(x))
+ },
+ isDefining = x.isDefining
+ )
+
+ implicit val format: Format[ApiCriterionLabel] = (
+ (JsPath \ "labelId").formatNullable[Long] and
+ (JsPath \ "categoryId").formatNullable[Long] and
+ (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ x =>
+ x == "Yes" || x == "No"
+ }), Writes.of[String])) and
+ (JsPath \ "isDefining").format[Boolean]
+ ) (ApiCriterionLabel.apply, unlift(ApiCriterionLabel.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala
new file mode 100644
index 0000000..9159d27
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala
@@ -0,0 +1,36 @@
+package xyz.driver.pdsuidomain.formats.json.label
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId}
+import xyz.driver.pdsuidomain.entities.{Category, ExtractedData, ExtractedDataLabel, Label}
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiExtractedDataLabel(id: Option[Long], categoryId: Option[Long], value: Option[String]) {
+
+ def toDomain(dataId: LongId[ExtractedData] = LongId(0)) = ExtractedDataLabel(
+ id = LongId(0),
+ dataId = dataId,
+ labelId = id.map(LongId[Label]),
+ categoryId = categoryId.map(LongId[Category]),
+ value = value.map(FuzzyValue.fromString)
+ )
+}
+
+object ApiExtractedDataLabel {
+
+ implicit val format: Format[ApiExtractedDataLabel] = (
+ (JsPath \ "id").formatNullable[Long] and
+ (JsPath \ "categoryId").formatNullable[Long] and
+ (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String]))
+ ) (ApiExtractedDataLabel.apply, unlift(ApiExtractedDataLabel.unapply))
+
+ def fromDomain(dataLabel: ExtractedDataLabel) = ApiExtractedDataLabel(
+ id = dataLabel.labelId.map(_.id),
+ categoryId = dataLabel.categoryId.map(_.id),
+ value = dataLabel.value.map(FuzzyValue.valueToString)
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala
new file mode 100644
index 0000000..8c30f3a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala
@@ -0,0 +1,22 @@
+package xyz.driver.pdsuidomain.formats.json.label
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.Label
+
+final case class ApiLabel(id: Long, name: String, categoryId: Long)
+
+object ApiLabel {
+
+ implicit val format: Format[ApiLabel] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "categoryId").format[Long]
+ ) (ApiLabel.apply, unlift(ApiLabel.unapply))
+
+ def fromDomain(x: Label) = ApiLabel(
+ id = x.id.id,
+ name = x.name,
+ categoryId = x.categoryId.id
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala
new file mode 100644
index 0000000..20b2607
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala
@@ -0,0 +1,59 @@
+package xyz.driver.pdsuidomain.formats.json.message
+
+import java.time.{ZoneId, ZonedDateTime}
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.Message
+
+final case class ApiMessage(id: Long,
+ text: String,
+ lastUpdate: ZonedDateTime,
+ userId: Long,
+ isDraft: Boolean,
+ recordId: Option[Long],
+ documentId: Option[Long],
+ patientId: Option[String],
+ trialId: Option[String],
+ startPage: Option[Double],
+ endPage: Option[Double],
+ evidence: Option[String],
+ archiveRequired: Option[Boolean],
+ meta: Option[String])
+
+object ApiMessage {
+
+ def fromDomain(domain: Message) = ApiMessage(
+ id = domain.id.id,
+ text = domain.text,
+ lastUpdate = ZonedDateTime.of(domain.lastUpdate, ZoneId.of("Z")),
+ userId = domain.userId.id,
+ isDraft = domain.isDraft,
+ recordId = domain.recordId.map(_.id),
+ documentId = domain.documentId.map(_.id),
+ patientId = domain.patientId.map(_.toString),
+ trialId = domain.trialId.map(_.toString),
+ startPage = domain.startPage,
+ endPage = domain.endPage,
+ evidence = domain.evidence,
+ archiveRequired = domain.archiveRequired,
+ meta = domain.meta
+ )
+
+ implicit val format: Format[ApiMessage] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "text").format[String] and
+ (JsPath \ "lastUpdate").format[ZonedDateTime] and
+ (JsPath \ "userId").format[Long] and
+ (JsPath \ "isDraft").format[Boolean] and
+ (JsPath \ "recordId").formatNullable[Long] and
+ (JsPath \ "documentId").formatNullable[Long] and
+ (JsPath \ "patientId").formatNullable[String] and
+ (JsPath \ "trialId").formatNullable[String] and
+ (JsPath \ "startPage").formatNullable[Double] and
+ (JsPath \ "endPage").formatNullable[Double] and
+ (JsPath \ "evidence").formatNullable[String] and
+ (JsPath \ "archiveRequired").formatNullable[Boolean] and
+ (JsPath \ "meta").formatNullable[String]
+ ) (ApiMessage.apply, unlift(ApiMessage.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala
new file mode 100644
index 0000000..151234c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala
@@ -0,0 +1,84 @@
+package xyz.driver.pdsuidomain.formats.json.message
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain._
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.Message
+
+final case class ApiPartialMessage(text: Option[String],
+ recordId: Option[Long],
+ documentId: Option[Long],
+ patientId: Option[String],
+ trialId: Option[String],
+ startPage: Option[Double],
+ endPage: Option[Double],
+ evidence: Option[String],
+ archiveRequired: Option[Boolean],
+ meta: Option[String]) {
+
+ def toDomain(userId: LongId[User]) = Message(
+ id = LongId(0),
+ text = text.getOrElse(""),
+ userId = userId,
+ isDraft = true,
+ recordId = recordId.map(LongId(_)),
+ documentId = documentId.map(LongId(_)),
+ patientId = patientId.map(UuidId(_)),
+ trialId = trialId.map(StringId(_)),
+ startPage = startPage,
+ endPage = endPage,
+ evidence = evidence,
+ archiveRequired = archiveRequired,
+ meta = meta,
+ lastUpdate = LocalDateTime.MIN
+ )
+
+ def applyTo(orig: Message): Message = {
+ orig.copy(
+ text = text.getOrElse(""),
+ recordId = recordId.map(LongId(_)),
+ documentId = documentId.map(LongId(_)),
+ patientId = patientId.map(UuidId(_)),
+ trialId = trialId.map(StringId(_)),
+ startPage = startPage,
+ endPage = endPage,
+ evidence = evidence,
+ archiveRequired = archiveRequired,
+ meta = meta,
+ lastUpdate = LocalDateTime.MIN
+ )
+ }
+
+}
+
+object ApiPartialMessage {
+
+ implicit val format: Format[ApiPartialMessage] = (
+ (JsPath \ "text").formatNullable[String] and
+ (JsPath \ "recordId").formatNullable[Long] and
+ (JsPath \ "documentId").formatNullable[Long] and
+ (JsPath \ "patientId").formatNullable[String] and
+ (JsPath \ "trialId").formatNullable[String] and
+ (JsPath \ "startPage").formatNullable[Double] and
+ (JsPath \ "endPage").formatNullable[Double] and
+ (JsPath \ "evidence").formatNullable[String] and
+ (JsPath \ "archiveRequired").formatNullable[Boolean] and
+ (JsPath \ "meta").formatNullable[String]
+ ) (ApiPartialMessage.apply, unlift(ApiPartialMessage.unapply))
+
+ def fromDomain(domain: Message) = ApiPartialMessage(
+ text = Some(domain.text),
+ recordId = domain.recordId.map(_.id),
+ documentId = domain.documentId.map(_.id),
+ patientId = domain.patientId.map(_.toString),
+ trialId = domain.trialId.map(_.toString),
+ startPage = domain.startPage,
+ endPage = domain.endPage,
+ evidence = domain.evidence,
+ archiveRequired = domain.archiveRequired,
+ meta = domain.meta
+ )
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala
new file mode 100644
index 0000000..5c12415
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala
@@ -0,0 +1,9 @@
+package xyz.driver.pdsuidomain.formats.json.password
+
+import play.api.libs.json.{Format, Json}
+
+final case class PasswordCreateRequest(password: String, key: String)
+
+object PasswordCreateRequest {
+ implicit val format: Format[PasswordCreateRequest] = Json.format[PasswordCreateRequest]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala
new file mode 100644
index 0000000..07851ea
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala
@@ -0,0 +1,9 @@
+package xyz.driver.pdsuidomain.formats.json.password
+
+import play.api.libs.json.{Format, Json}
+
+final case class PasswordUpdateRequest(password: String, oldPassword: String)
+
+object PasswordUpdateRequest {
+ implicit val format: Format[PasswordUpdateRequest] = Json.format[PasswordUpdateRequest]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala
new file mode 100644
index 0000000..0a3938c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala
@@ -0,0 +1,44 @@
+package xyz.driver.pdsuidomain.formats.json.patient
+
+import java.time.{LocalDate, ZoneId, ZonedDateTime}
+
+import xyz.driver.pdsuidomain.entities.Patient
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+
+final case class ApiPatient(id: String,
+ status: String,
+ name: String,
+ dob: LocalDate,
+ assignee: Option[Long],
+ previousStatus: Option[String],
+ previousAssignee: Option[Long],
+ lastUpdate: ZonedDateTime,
+ condition: String)
+
+object ApiPatient {
+
+ implicit val format: Format[ApiPatient] = (
+ (JsPath \ "id").format[String] and
+ (JsPath \ "status").format[String] and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "dob").format[LocalDate] and
+ (JsPath \ "assignee").formatNullable[Long] and
+ (JsPath \ "previousStatus").formatNullable[String] and
+ (JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "lastUpdate").format[ZonedDateTime] and
+ (JsPath \ "condition").format[String]
+ ) (ApiPatient.apply, unlift(ApiPatient.unapply))
+
+ def fromDomain(patient: Patient) = ApiPatient(
+ id = patient.id.toString,
+ status = PatientStatus.statusToString(patient.status),
+ name = patient.name,
+ dob = patient.dob,
+ assignee = patient.assignee.map(_.id),
+ previousStatus = patient.previousStatus.map(PatientStatus.statusToString),
+ previousAssignee = patient.previousAssignee.map(_.id),
+ lastUpdate = ZonedDateTime.of(patient.lastUpdate, ZoneId.of("Z")),
+ condition = patient.condition
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala
new file mode 100644
index 0000000..d906fc6
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala
@@ -0,0 +1,24 @@
+package xyz.driver.pdsuidomain.formats.json.patient
+
+import xyz.driver.pdsuidomain.entities.Patient.Status
+
+object PatientStatus {
+
+ val statusFromString: PartialFunction[String, Status] = {
+ case "New" => Status.New
+ case "Verified" => Status.Verified
+ case "Reviewed" => Status.Reviewed
+ case "Curated" => Status.Curated
+ case "Flagged" => Status.Flagged
+ case "Done" => Status.Done
+ }
+
+ def statusToString(x: Status): String = x match {
+ case Status.New => "New"
+ case Status.Verified => "Verified"
+ case Status.Reviewed => "Reviewed"
+ case Status.Curated => "Curated"
+ case Status.Flagged => "Flagged"
+ case Status.Done => "Done"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala
new file mode 100644
index 0000000..03ff275
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala
@@ -0,0 +1,18 @@
+package xyz.driver.pdsuidomain.formats.json.patient.eligible
+
+import xyz.driver.pdsuidomain.entities.PatientTrialArmGroupView
+import play.api.libs.json.{Format, Json}
+
+final case class ApiPartialPatientEligibleTrial(isVerified: Option[Boolean]) {
+
+ def applyTo(orig: PatientTrialArmGroupView): PatientTrialArmGroupView = {
+ orig.copy(
+ isVerified = isVerified.getOrElse(orig.isVerified)
+ )
+ }
+}
+
+object ApiPartialPatientEligibleTrial {
+
+ implicit val format: Format[ApiPartialPatientEligibleTrial] = Json.format
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala
new file mode 100644
index 0000000..c1a6e76
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala
@@ -0,0 +1,46 @@
+package xyz.driver.pdsuidomain.formats.json.patient.eligible
+
+import java.util.UUID
+
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+import xyz.driver.pdsuidomain.services.PatientEligibleTrialService.RichPatientEligibleTrial
+
+final case class ApiPatientEligibleTrial(id: Long,
+ patientId: String,
+ trialId: String,
+ trialTitle: String,
+ arms: List[String],
+ hypothesisId: UUID,
+ eligibilityStatus: Option[String],
+ isVerified: Boolean)
+
+object ApiPatientEligibleTrial {
+
+ implicit val apiEligibleTrialJsonFormat: Format[ApiPatientEligibleTrial] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "patientId").format[String] and
+ (JsPath \ "trialId").format[String] and
+ (JsPath \ "trialTitle").format[String] and
+ (JsPath \ "arms").format[List[String]] and
+ (JsPath \ "hypothesisId").format[UUID] and
+ (JsPath \ "eligibilityStatus").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown eligibility status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "isVerified").format[Boolean]
+ ) (ApiPatientEligibleTrial.apply, unlift(ApiPatientEligibleTrial.unapply))
+
+ def fromDomain(eligibleTrialWithTrial: RichPatientEligibleTrial) = ApiPatientEligibleTrial(
+ id = eligibleTrialWithTrial.group.id.id,
+ patientId = eligibleTrialWithTrial.group.patientId.toString,
+ trialId = eligibleTrialWithTrial.group.trialId.id,
+ trialTitle = eligibleTrialWithTrial.trial.title,
+ arms = eligibleTrialWithTrial.arms.map(_.name),
+ hypothesisId = eligibleTrialWithTrial.group.hypothesisId.id,
+ eligibleTrialWithTrial.group.eligibilityStatus.map(FuzzyValue.valueToString),
+ eligibleTrialWithTrial.group.isVerified
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala
new file mode 100644
index 0000000..0858ce1
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala
@@ -0,0 +1,27 @@
+package xyz.driver.pdsuidomain.formats.json.patient.hypothesis
+
+import xyz.driver.pdsuidomain.entities.PatientHypothesis
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiPartialPatientHypothesis(rationale: Tristate[String]) {
+
+ def applyTo(orig: PatientHypothesis): PatientHypothesis = {
+ orig.copy(
+ rationale = rationale.cata(Some(_), None, orig.rationale)
+ )
+ }
+}
+
+object ApiPartialPatientHypothesis {
+
+ implicit val reads: Reads[ApiPartialPatientHypothesis] =
+ (__ \ "rationale").readTristate[String].map(x => ApiPartialPatientHypothesis(x))
+
+ implicit val writes: Writes[ApiPartialPatientHypothesis] =
+ (__ \ "rationale").writeTristate[String].contramap(_.rationale)
+
+ implicit val format: Format[ApiPartialPatientHypothesis] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala
new file mode 100644
index 0000000..1b0767d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.formats.json.patient.hypothesis
+
+import java.util.UUID
+
+import xyz.driver.pdsuidomain.entities.PatientHypothesis
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiPatientHypothesis(id: UUID,
+ patientId: String,
+ hypothesisId: UUID,
+ matchedTrials: Long,
+ rationale: Option[String])
+
+object ApiPatientHypothesis {
+
+ implicit val apiPatientHypothesisJsonFormat: Format[ApiPatientHypothesis] = (
+ (JsPath \ "id").format[UUID] and
+ (JsPath \ "patientId").format[String] and
+ (JsPath \ "hypothesisId").format[UUID] and
+ (JsPath \ "matchedTrials").format[Long] and
+ (JsPath \ "rationale").formatNullable[String]
+ ) (ApiPatientHypothesis.apply, unlift(ApiPatientHypothesis.unapply))
+
+ def fromDomain(patientHypothesis: PatientHypothesis) = ApiPatientHypothesis(
+ id = patientHypothesis.id.id,
+ patientId = patientHypothesis.patientId.toString,
+ hypothesisId = patientHypothesis.hypothesisId.id,
+ matchedTrials = patientHypothesis.matchedTrials,
+ rationale = patientHypothesis.rationale
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala
new file mode 100644
index 0000000..82e3a3f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala
@@ -0,0 +1,38 @@
+package xyz.driver.pdsuidomain.formats.json.patient.label
+
+import xyz.driver.pdsuidomain.entities.PatientLabel
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+
+final case class ApiPartialPatientLabel(primaryValue: Option[String],
+ verifiedPrimaryValue: Tristate[String]) {
+
+ def applyTo(orig: PatientLabel): PatientLabel = {
+ orig.copy(
+ primaryValue = primaryValue.map(FuzzyValue.fromString).orElse(orig.primaryValue),
+ verifiedPrimaryValue = verifiedPrimaryValue.cata(x => Some(FuzzyValue.fromString(x)), None, orig.verifiedPrimaryValue)
+ )
+ }
+
+}
+
+object ApiPartialPatientLabel {
+
+ implicit val format: Format[ApiPartialPatientLabel] = (
+ (JsPath \ "primaryValue").formatNullable[String](Format(
+ Reads.of[String].filter(ValidationError("unknown primary value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "verifiedPrimaryValue").formatTristate[String](Format(
+ Reads.of[String].filter(ValidationError("unknown verified primary value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String]))
+ ) (ApiPartialPatientLabel.apply, unlift(ApiPartialPatientLabel.unapply))
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala
new file mode 100644
index 0000000..fc8687b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala
@@ -0,0 +1,47 @@
+package xyz.driver.pdsuidomain.formats.json.patient.label
+
+import xyz.driver.pdsuidomain.entities.PatientLabel
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+
+final case class ApiPatientLabel(id: Long,
+ labelId: Long,
+ primaryValue: Option[String],
+ verifiedPrimaryValue: Option[String],
+ score: Int,
+ isImplicitMatch: Boolean,
+ isVisible: Boolean,
+ isVerified: Boolean)
+
+object ApiPatientLabel {
+
+ implicit val apiPatientLabelJsonFormat: Format[ApiPatientLabel] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "labelId").format[Long] and
+ (JsPath \ "primaryValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "verifiedPrimaryValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "score").format[Int] and
+ (JsPath \ "isImplicitMatch").format[Boolean] and
+ (JsPath \ "isVisible").format[Boolean] and
+ (JsPath \ "isVerified").format[Boolean]
+ ) (ApiPatientLabel.apply, unlift(ApiPatientLabel.unapply))
+
+ def fromDomain(patientLabel: PatientLabel, isVerified: Boolean): ApiPatientLabel = ApiPatientLabel(
+ id = patientLabel.id.id,
+ labelId = patientLabel.labelId.id,
+ primaryValue = patientLabel.primaryValue.map(FuzzyValue.valueToString),
+ verifiedPrimaryValue = patientLabel.verifiedPrimaryValue.map(FuzzyValue.valueToString),
+ score = patientLabel.score,
+ isImplicitMatch = patientLabel.isImplicitMatch,
+ isVisible = patientLabel.isVisible,
+ isVerified = isVerified
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala
new file mode 100644
index 0000000..3fe135e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala
@@ -0,0 +1,25 @@
+package xyz.driver.pdsuidomain.formats.json.patient.label
+
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+import xyz.driver.pdsuidomain.entities.PatientLabel
+
+final case class ApiPatientLabelDefiningCriteria(labelId: Long, value: Option[String])
+
+object ApiPatientLabelDefiningCriteria {
+
+ implicit val format: Format[ApiPatientLabelDefiningCriteria] = (
+ (JsPath \ "labelId").format[Long] and
+ (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String]))
+ ) (ApiPatientLabelDefiningCriteria.apply, unlift(ApiPatientLabelDefiningCriteria.unapply))
+
+ def fromDomain(x: PatientLabel) = ApiPatientLabelDefiningCriteria(
+ labelId = x.labelId.id,
+ value = x.verifiedPrimaryValue.map(FuzzyValue.valueToString)
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala
new file mode 100644
index 0000000..b68dad5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala
@@ -0,0 +1,40 @@
+package xyz.driver.pdsuidomain.formats.json.patient.trial
+
+import xyz.driver.pdsuidomain.entities.PatientCriterion
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath, Reads, Writes}
+import xyz.driver.pdsuicommon.domain.FuzzyValue
+
+final case class ApiPartialPatientCriterion(eligibilityStatus: Option[String],
+ verifiedEligibilityStatus: Tristate[String]) {
+
+ def applyTo(orig: PatientCriterion): PatientCriterion = {
+ orig.copy(
+ eligibilityStatus = eligibilityStatus.map(FuzzyValue.fromString).orElse(orig.eligibilityStatus),
+ verifiedEligibilityStatus = verifiedEligibilityStatus.cata(x =>
+ Some(FuzzyValue.fromString(x)),
+ None,
+ orig.verifiedEligibilityStatus
+ )
+ )
+ }
+}
+
+object ApiPartialPatientCriterion {
+
+ implicit val format: Format[ApiPartialPatientCriterion] = (
+ (JsPath \ "eligibilityStatus").formatNullable[String](Format(
+ Reads.of[String].filter(ValidationError("unknown eligibility status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "verifiedEligibilityStatus").formatTristate[String](Format(
+ Reads.of[String].filter(ValidationError("unknown verified eligibility status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String]))
+ ) (ApiPartialPatientCriterion.apply, unlift(ApiPartialPatientCriterion.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala
new file mode 100644
index 0000000..71cb58f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.formats.json.patient.trial
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId}
+import xyz.driver.pdsuidomain.entities.PatientCriterion
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath, Reads, Writes}
+import xyz.driver.pdsuidomain.services.PatientCriterionService.DraftPatientCriterion
+
+final case class ApiPartialPatientCriterionList(id: Long,
+ eligibilityStatus: Option[String],
+ isVerified: Option[Boolean]) {
+
+ def toDomain: DraftPatientCriterion = DraftPatientCriterion(
+ id = LongId[PatientCriterion](id),
+ eligibilityStatus = eligibilityStatus.map(FuzzyValue.fromString),
+ isVerified = isVerified
+ )
+}
+
+object ApiPartialPatientCriterionList {
+
+ implicit val format: Format[ApiPartialPatientCriterionList] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "eligibilityStatus").formatNullable[String](Format(
+ Reads.of[String].filter(ValidationError("unknown eligibility status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "isVerified").formatNullable[Boolean]
+ ) (ApiPartialPatientCriterionList.apply, unlift(ApiPartialPatientCriterionList.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala
new file mode 100644
index 0000000..3e2de99
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala
@@ -0,0 +1,72 @@
+package xyz.driver.pdsuidomain.formats.json.patient.trial
+
+import java.time.{ZoneId, ZonedDateTime}
+
+import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId}
+import xyz.driver.pdsuidomain.entities.{Arm, Label, PatientCriterion}
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath, Reads, Writes}
+
+final case class ApiPatientCriterion(id: Long,
+ labelId: Long,
+ nctId: String,
+ criterionText: String,
+ criterionValue: Option[String],
+ criterionIsDefining: Boolean,
+ criterionIsCompound: Boolean,
+ arms: List[String],
+ eligibilityStatus: Option[String],
+ verifiedEligibilityStatus: Option[String],
+ isVerified: Boolean,
+ isVisible: Boolean,
+ lastUpdate: ZonedDateTime)
+
+object ApiPatientCriterion {
+
+ implicit val format: Format[ApiPatientCriterion] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "labelId").format[Long] and
+ (JsPath \ "nctId").format[String] and
+ (JsPath \ "criterionText").format[String] and
+ (JsPath \ "criterionValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ x =>
+ x == "Yes" || x == "No"
+ }), Writes.of[String])) and
+ (JsPath \ "criterionIsDefining").format[Boolean] and
+ (JsPath \ "criterionIsCompound").format[Boolean] and
+ (JsPath \ "arms").format[List[String]] and
+ (JsPath \ "eligibilityStatus").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "verifiedEligibilityStatus").formatNullable[String](Format(
+ Reads.of[String].filter(ValidationError("unknown status"))({
+ case x if FuzzyValue.fromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "isVerified").format[Boolean] and
+ (JsPath \ "isVisible").format[Boolean] and
+ (JsPath \ "lastUpdate").format[ZonedDateTime]
+ ) (ApiPatientCriterion.apply, unlift(ApiPatientCriterion.unapply))
+
+ def fromDomain(patientCriterion: PatientCriterion,
+ labelId: LongId[Label],
+ arms: List[Arm],
+ criterionIsCompound: Boolean) = ApiPatientCriterion(
+ id = patientCriterion.id.id,
+ labelId = labelId.id,
+ nctId = patientCriterion.nctId.id,
+ criterionText = patientCriterion.criterionText,
+ criterionValue = patientCriterion.criterionValue.map { x =>
+ FuzzyValue.valueToString(FuzzyValue.fromBoolean(x))
+ },
+ criterionIsDefining = patientCriterion.criterionIsDefining,
+ criterionIsCompound = criterionIsCompound,
+ arms = arms.map(_.name),
+ eligibilityStatus = patientCriterion.eligibilityStatus.map(FuzzyValue.valueToString),
+ verifiedEligibilityStatus = patientCriterion.verifiedEligibilityStatus.map(FuzzyValue.valueToString),
+ isVerified = patientCriterion.isVerified,
+ isVisible = patientCriterion.isVisible,
+ lastUpdate = ZonedDateTime.of(patientCriterion.lastUpdate, ZoneId.of("Z"))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala
new file mode 100644
index 0000000..57c030b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala
@@ -0,0 +1,37 @@
+package xyz.driver.pdsuidomain.formats.json.record
+
+import java.time.LocalDateTime
+import java.util.UUID
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuidomain.entities._
+import play.api.libs.json._
+
+final case class ApiCreateRecord(disease: String,
+ patientId: String,
+ requestId: UUID,
+ filename: String) {
+
+ def toDomain = MedicalRecord(
+ id = LongId(0),
+ status = MedicalRecord.Status.New,
+ previousStatus = None,
+ assignee = None,
+ previousAssignee = None,
+ patientId = UuidId(patientId),
+ requestId = RecordRequestId(requestId),
+ disease = disease,
+ caseId = None,
+ physician = None,
+ sourceName = filename,
+ meta = None,
+ predictedMeta = None,
+ predictedDocuments = None,
+ lastUpdate = LocalDateTime.now()
+ )
+}
+
+object ApiCreateRecord {
+
+ implicit val format: Format[ApiCreateRecord] = Json.format[ApiCreateRecord]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala
new file mode 100644
index 0000000..6096006
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala
@@ -0,0 +1,57 @@
+package xyz.driver.pdsuidomain.formats.json.record
+
+import java.time.{ZoneId, ZonedDateTime}
+
+import xyz.driver.pdsuidomain.entities.MedicalRecord
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.JsonSerializer
+
+final case class ApiRecord(id: Long,
+ patientId: String,
+ caseId: Option[String],
+ physician: Option[String],
+ lastUpdate: ZonedDateTime,
+ status: String,
+ previousStatus: Option[String],
+ assignee: Option[Long],
+ previousAssignee: Option[Long],
+ meta: String)
+
+object ApiRecord {
+
+ private val statusFormat = Format(
+ Reads.StringReads.filter(ValidationError("unknown status")) {
+ case x if MedicalRecordStatus.statusFromString.isDefinedAt(x) => true
+ case _ => false
+ },
+ Writes.StringWrites
+ )
+
+ implicit val format: Format[ApiRecord] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "patientId").format[String] and
+ (JsPath \ "caseId").formatNullable[String] and
+ (JsPath \ "physician").formatNullable[String] and
+ (JsPath \ "lastUpdate").format[ZonedDateTime] and
+ (JsPath \ "status").format(statusFormat) and
+ (JsPath \ "previousStatus").formatNullable(statusFormat) and
+ (JsPath \ "assignee").formatNullable[Long] and
+ (JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "meta").format(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse)))
+ ) (ApiRecord.apply, unlift(ApiRecord.unapply))
+
+ def fromDomain(record: MedicalRecord) = ApiRecord(
+ id = record.id.id,
+ patientId = record.patientId.toString,
+ caseId = record.caseId.map(_.id),
+ physician = record.physician,
+ lastUpdate = ZonedDateTime.of(record.lastUpdate, ZoneId.of("Z")),
+ status = MedicalRecordStatus.statusToString(record.status),
+ previousStatus = record.previousStatus.map(MedicalRecordStatus.statusToString),
+ assignee = record.assignee.map(_.id),
+ previousAssignee = record.previousAssignee.map(_.id),
+ meta = record.meta.map(x => JsonSerializer.serialize(x.content)).getOrElse("[]")
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala
new file mode 100644
index 0000000..b752a4c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala
@@ -0,0 +1,47 @@
+package xyz.driver.pdsuidomain.formats.json.record
+
+import xyz.driver.pdsuidomain.entities.MedicalRecord.Meta
+import xyz.driver.pdsuidomain.entities._
+import xyz.driver.pdsuicommon.domain.{LongId, TextJson, User}
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.JsonSerializer
+
+final case class ApiUpdateRecord(status: Option[String],
+ assignee: Tristate[Long],
+ meta: Tristate[String]) {
+
+ def applyTo(orig: MedicalRecord): MedicalRecord = {
+ orig.copy(
+ status = status.map(MedicalRecordStatus.statusFromString).getOrElse(orig.status),
+ assignee = assignee.map(LongId[User]).cata(Some(_), None, orig.assignee),
+ meta = meta.cata(x => Some(TextJson(JsonSerializer.deserialize[List[Meta]](x))), None, orig.meta)
+ )
+ }
+}
+
+object ApiUpdateRecord {
+
+ private val reads: Reads[ApiUpdateRecord] = (
+ (JsPath \ "status").readNullable[String](Reads.of[String].filter(ValidationError("unknown status"))({
+ case x if MedicalRecordStatus.statusFromString.isDefinedAt(x) => true
+ case _ => false
+ })) and
+ (JsPath \ "assignee").readTristate[Long] and
+ (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map {
+ case Tristate.Present("{}") => Tristate.Absent
+ case x => x
+ }
+ ) (ApiUpdateRecord.apply _)
+
+ private val writes: Writes[ApiUpdateRecord] = (
+ (JsPath \ "status").writeNullable[String] and
+ (JsPath \ "assignee").writeTristate[Long] and
+ (JsPath \ "meta").writeTristate(Writes[String](Json.parse))
+ ) (unlift(ApiUpdateRecord.unapply))
+
+ implicit val format: Format[ApiUpdateRecord] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala
new file mode 100644
index 0000000..bde4af0
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala
@@ -0,0 +1,34 @@
+package xyz.driver.pdsuidomain.formats.json.record
+
+import xyz.driver.pdsuidomain.entities.MedicalRecord.Status
+
+object MedicalRecordStatus {
+
+ val statusFromString: PartialFunction[String, Status] = {
+ case "Unprocessed" => Status.Unprocessed
+ case "PreCleaning" => Status.PreCleaning
+ case "New" => Status.New
+ case "Cleaned" => Status.Cleaned
+ case "PreOrganized" => Status.PreOrganized
+ case "PreOrganizing" => Status.PreOrganizing
+ case "Reviewed" => Status.Reviewed
+ case "Organized" => Status.Organized
+ case "Done" => Status.Done
+ case "Flagged" => Status.Flagged
+ case "Archived" => Status.Archived
+ }
+
+ def statusToString(x: Status): String = x match {
+ case Status.Unprocessed => "Unprocessed"
+ case Status.PreCleaning => "PreCleaning"
+ case Status.New => "New"
+ case Status.Cleaned => "Cleaned"
+ case Status.PreOrganized => "PreOrganized"
+ case Status.PreOrganizing => "PreOrganizing"
+ case Status.Reviewed => "Reviewed"
+ case Status.Organized => "Organized"
+ case Status.Done => "Done"
+ case Status.Flagged => "Flagged"
+ case Status.Archived => "Archived"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala
new file mode 100644
index 0000000..fbe9689
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala
@@ -0,0 +1,12 @@
+package xyz.driver.pdsuidomain.formats.json.session
+
+import xyz.driver.pdsuicommon.domain.Email
+import play.api.libs.json._
+import xyz.driver.pdsuicommon.json.Serialization._
+
+final case class NewSessionRequest(email: Email, password: String)
+
+object NewSessionRequest {
+
+ implicit val format: Format[NewSessionRequest] = Json.format[NewSessionRequest]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala
new file mode 100644
index 0000000..55e05b1
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala
@@ -0,0 +1,11 @@
+package xyz.driver.pdsuidomain.formats.json.session
+
+import play.api.libs.json.Json
+import xyz.driver.pdsuidomain.formats.json.user.ApiUser
+
+final case class NewSessionResponse(token: String, user: ApiUser)
+
+object NewSessionResponse {
+
+ implicit val format = Json.format[NewSessionResponse]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala
new file mode 100644
index 0000000..958ff5d
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala
@@ -0,0 +1,20 @@
+package xyz.driver.pdsuidomain.formats.json.studydesign
+
+import xyz.driver.pdsuidomain.entities.StudyDesign
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+
+final case class ApiStudyDesign(id: Long, name: String)
+
+object ApiStudyDesign {
+
+ implicit val format: Format[ApiStudyDesign] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "name").format[String]
+ ) (ApiStudyDesign.apply, unlift(ApiStudyDesign.unapply))
+
+ def fromDomain(studyDesign: StudyDesign) = ApiStudyDesign(
+ id = studyDesign.id.id,
+ name = studyDesign.name
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala
new file mode 100644
index 0000000..0dc1446
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala
@@ -0,0 +1,44 @@
+package xyz.driver.pdsuidomain.formats.json.trial
+
+import java.util.UUID
+
+import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
+import xyz.driver.pdsuidomain.entities.Trial
+import org.davidbild.tristate.Tristate
+import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiPartialTrial(hypothesisId: Tristate[UUID],
+ studyDesignId: Tristate[Long],
+ overview: Tristate[String],
+ title: Tristate[String]) {
+
+ def applyTo(orig: Trial): Trial = {
+ orig.copy(
+ hypothesisId = hypothesisId.map(UuidId(_)).cata(Some(_), None, orig.hypothesisId),
+ studyDesignId = studyDesignId.map(LongId(_)).cata(Some(_), None, orig.studyDesignId),
+ overview = overview.cata(Some(_), None, orig.overview),
+ title = title.cata(Some(_).getOrElse(""), "", orig.title)
+ )
+ }
+}
+
+object ApiPartialTrial {
+
+ private val reads: Reads[ApiPartialTrial] = (
+ (JsPath \ "hypothesisId").readTristate[UUID] and
+ (JsPath \ "studyDesignId").readTristate[Long] and
+ (JsPath \ "overview").readTristate[String] and
+ (JsPath \ "title").readTristate[String]
+ ) (ApiPartialTrial.apply _)
+
+ private val writes: Writes[ApiPartialTrial] = (
+ (JsPath \ "hypothesisId").writeTristate[UUID] and
+ (JsPath \ "studyDesignId").writeTristate[Long] and
+ (JsPath \ "overview").writeTristate[String] and
+ (JsPath \ "title").writeTristate[String]
+ ) (unlift(ApiPartialTrial.unapply))
+
+ implicit val format: Format[ApiPartialTrial] = Format(reads, writes)
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala
new file mode 100644
index 0000000..3267617
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala
@@ -0,0 +1,63 @@
+package xyz.driver.pdsuidomain.formats.json.trial
+
+import java.time.{ZoneId, ZonedDateTime}
+import java.util.UUID
+
+import xyz.driver.pdsuidomain.entities.Trial
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiTrial(id: String,
+ lastUpdate: Option[ZonedDateTime],
+ status: String,
+ assignee: Option[Long],
+ previousStatus: Option[String],
+ previousAssignee: Option[Long],
+ condition: Option[String],
+ phase: Option[String],
+ hypothesisId: Option[UUID],
+ studyDesignId: Option[Long],
+ isPartner: Boolean,
+ overview: Option[String],
+ overviewTemplate: String,
+ isUpdated: Boolean,
+ title: String)
+
+object ApiTrial {
+
+ implicit val format: Format[ApiTrial] = (
+ (JsPath \ "id").format[String] and
+ (JsPath \ "lastUpdate").formatNullable[ZonedDateTime] and
+ (JsPath \ "status").format[String] and
+ (JsPath \ "assignee").formatNullable[Long] and
+ (JsPath \ "previousStatus").formatNullable[String] and
+ (JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "condition").formatNullable[String] and
+ (JsPath \ "phase").formatNullable[String] and
+ (JsPath \ "hypothesisId").formatNullable[UUID] and
+ (JsPath \ "studyDesignId").formatNullable[Long] and
+ (JsPath \ "isPartner").format[Boolean] and
+ (JsPath \ "overview").formatNullable[String] and
+ (JsPath \ "overviewTemplate").format[String] and
+ (JsPath \ "isUpdated").format[Boolean] and
+ (JsPath \ "title").format[String]
+ ) (ApiTrial.apply, unlift(ApiTrial.unapply))
+
+ def fromDomain(trial: Trial): ApiTrial = ApiTrial(
+ id = trial.id.id,
+ status = TrialStatus.statusToString(trial.status),
+ assignee = trial.assignee.map(_.id),
+ previousStatus = trial.previousStatus.map(TrialStatus.statusToString),
+ previousAssignee = trial.previousAssignee.map(_.id),
+ lastUpdate = Option(ZonedDateTime.of(trial.lastUpdate, ZoneId.of("Z"))),
+ condition = Option(trial.condition.toString),
+ phase = Option(trial.phase),
+ hypothesisId = trial.hypothesisId.map(_.id),
+ studyDesignId = trial.studyDesignId.map(_.id),
+ isPartner = trial.isPartner,
+ overview = trial.overview,
+ overviewTemplate = trial.overviewTemplate,
+ isUpdated = trial.isUpdated,
+ title = trial.title
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala
new file mode 100644
index 0000000..49bcbcb
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala
@@ -0,0 +1,30 @@
+package xyz.driver.pdsuidomain.formats.json.trial
+
+import xyz.driver.pdsuidomain.entities.Trial.Status
+
+object TrialStatus {
+
+ val statusFromString: PartialFunction[String, Status] = {
+ case "New" => Status.New
+ case "ReviewSummary" => Status.ReviewSummary
+ case "Summarized" => Status.Summarized
+ case "PendingUpdate" => Status.PendingUpdate
+ case "Update" => Status.Update
+ case "ReviewCriteria" => Status.ReviewCriteria
+ case "Done" => Status.Done
+ case "Flagged" => Status.Flagged
+ case "Archived" => Status.Archived
+ }
+
+ def statusToString(x: Status): String = x match {
+ case Status.New => "New"
+ case Status.ReviewSummary => "ReviewSummary"
+ case Status.Summarized => "Summarized"
+ case Status.PendingUpdate => "PendingUpdate"
+ case Status.Update => "Update"
+ case Status.ReviewCriteria => "ReviewCriteria"
+ case Status.Done => "Done"
+ case Status.Flagged => "Flagged"
+ case Status.Archived => "Archived"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala
new file mode 100644
index 0000000..977934b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala
@@ -0,0 +1,83 @@
+package xyz.driver.pdsuidomain.formats.json.user
+
+import java.math.BigInteger
+import java.security.SecureRandom
+
+import xyz.driver.pdsuicommon.domain.{Email, LongId, PasswordHash, User}
+import play.api.data.validation._
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+import scala.collection._
+import scala.util.Try
+import ApiPartialUser._
+import xyz.driver.pdsuicommon.json.JsonValidationException
+import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors}
+
+final case class ApiPartialUser(email: Option[String],
+ name: Option[String],
+ roleId: Option[String]) {
+
+ def applyTo(orig: User): Try[User] = Try {
+ val validation = Map(
+ JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name)
+ )
+
+ val validationErrors: JsonValidationErrors = validation.collect({
+ case (fieldName, e: Invalid) => (fieldName, e.errors)
+ })(breakOut)
+
+ if (validationErrors.isEmpty) {
+ orig.copy(name = name.get)
+ } else {
+ throw new JsonValidationException(validationErrors)
+ }
+ }
+
+ def toDomain(id: LongId[User] = LongId(0L)): Try[User] = Try {
+ val validation = Map(
+ JsPath \ "email" -> AdditionalConstraints.optionNonEmptyConstraint(email),
+ JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name),
+ JsPath \ "roleId" -> AdditionalConstraints.optionNonEmptyConstraint(roleId)
+ )
+
+ val validationErrors: JsonValidationErrors = validation.collect({
+ case (fieldName, e: Invalid) => (fieldName, e.errors)
+ })(breakOut)
+
+ if (validationErrors.isEmpty) {
+ val userEmail = email.map(x => Email(x.toLowerCase)).get
+ User(
+ id = id,
+ email = userEmail,
+ name = name.get,
+ role = roleId.map(UserRole.roleFromString).get,
+ passwordHash = PasswordHash(createPassword),
+ latestActivity = None,
+ deleted = None
+ )
+ } else {
+ throw new JsonValidationException(validationErrors)
+ }
+ }
+}
+
+object ApiPartialUser {
+
+ // SecureRandom is thread-safe, see the implementation
+ private val random = new SecureRandom()
+
+ def createPassword: String = new BigInteger(240, random).toString(32)
+
+ implicit val format: Format[ApiPartialUser] = (
+ (JsPath \ "email").formatNullable[String](Format(Reads.email, Writes.StringWrites)) and
+ (JsPath \ "name").formatNullable[String](Format(
+ Reads.filterNot[String](ValidationError("Username is too long (max length is 255 chars)", 255))(_.length > 255),
+ Writes.StringWrites
+ )) and
+ (JsPath \ "roleId").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown role"))({
+ case x if UserRole.roleFromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String]))
+ ) (ApiPartialUser.apply, unlift(ApiPartialUser.unapply))
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala
new file mode 100644
index 0000000..c2653ec
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.formats.json.user
+
+import java.time.{ZoneId, ZonedDateTime}
+
+import xyz.driver.pdsuicommon.domain.User
+import play.api.data.validation.ValidationError
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+final case class ApiUser(id: Long, email: String, name: String, roleId: String, latestActivity: Option[ZonedDateTime])
+
+object ApiUser {
+
+ implicit val format: Format[ApiUser] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "email").format[String](Reads.email) and
+ (JsPath \ "name").format[String] and
+ (JsPath \ "roleId").format[String](Format(Reads.of[String].filter(ValidationError("unknown role"))({
+ case x if UserRole.roleFromString.isDefinedAt(x) => true
+ case _ => false
+ }), Writes.of[String])) and
+ (JsPath \ "latestActivity").formatNullable[ZonedDateTime]
+ ) (ApiUser.apply, unlift(ApiUser.unapply))
+
+ def fromDomain(user: User) = ApiUser(
+ user.id.id,
+ user.email.value,
+ user.name,
+ UserRole.roleToString(user.role),
+ user.latestActivity.map(ZonedDateTime.of(_, ZoneId.of("Z")))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala
new file mode 100644
index 0000000..74acb81
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala
@@ -0,0 +1,33 @@
+package xyz.driver.pdsuidomain.formats.json.user
+
+import xyz.driver.pdsuicommon.domain.User.Role
+
+object UserRole {
+
+ val roleFromString: PartialFunction[String, Role] = {
+ case "Cleaner" => Role.RecordCleaner
+ case "Organizer" => Role.RecordOrganizer
+ case "Extractor" => Role.DocumentExtractor
+ case "RecordAdmin" => Role.RecordAdmin
+ case "TrialSummarizer" => Role.TrialSummarizer
+ case "CriteriaCurator" => Role.CriteriaCurator
+ case "TrialAdmin" => Role.TrialAdmin
+ case "EligibilityVerifier" => Role.EligibilityVerifier
+ case "TreatmentMatchingAdmin" => Role.TreatmentMatchingAdmin
+ case "RoutesCurator" => Role.RoutesCurator
+ // No Mixed at this time
+ }
+
+ def roleToString(x: Role): String = x match {
+ case Role.RecordCleaner => "Cleaner"
+ case Role.RecordOrganizer => "Organizer"
+ case Role.DocumentExtractor => "Extractor"
+ case Role.RecordAdmin => "RecordAdmin"
+ case Role.TrialSummarizer => "TrialSummarizer"
+ case Role.CriteriaCurator => "CriteriaCurator"
+ case Role.TrialAdmin => "TrialAdmin"
+ case Role.EligibilityVerifier => "EligibilityVerifier"
+ case Role.TreatmentMatchingAdmin => "TreatmentMatchingAdmin"
+ case Role.RoutesCurator => "RoutesCurator"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala
new file mode 100644
index 0000000..bc59f0c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala
@@ -0,0 +1,133 @@
+package xyz.driver.pdsuidomain.services
+
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Arm
+
+import scala.concurrent.Future
+
+object ArmService {
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Arm not found"
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+
+ case class Entity(x: Arm) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ type Error = GetListReply with DomainError
+
+ case class EntityList(xs: Seq[Arm], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+
+ case class Updated(updated: Arm) extends UpdateReply
+
+ type Error = UpdateReply with DomainError
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ case class AlreadyExistsError(x: Arm) extends UpdateReply with DomainError {
+ val userMessage = s"The arm with such name of trial already exists."
+ }
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ case class Created(x: Arm) extends CreateReply
+
+ type Error = CreateReply with DomainError
+
+ case object AuthorizationError
+ extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends CreateReply with DomainError
+
+ case class AlreadyExistsError(x: Arm) extends CreateReply with DomainError {
+ val userMessage = s"The arm with this name of trial already exists."
+ }
+
+ implicit def toPhiString(reply: CreateReply): PhiString = reply match {
+ case Created(x) => phi"Created($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+
+ sealed trait DeleteReply
+ object DeleteReply {
+ case object Deleted extends DeleteReply
+
+ type Error = DeleteReply with DomainError
+
+ case object NotFoundError
+ extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends DeleteReply with DomainError
+ }
+}
+
+trait ArmService {
+
+ import ArmService._
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getById(armId: LongId[Arm])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def create(draftArm: Arm)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def update(origArm: Arm, draftArm: Arm)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(id: LongId[Arm])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala
new file mode 100644
index 0000000..e3d806c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala
@@ -0,0 +1,131 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object CriterionService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Criterion not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ case class RichCriterion(criterion: Criterion,
+ armIds: Seq[LongId[Arm]],
+ labels: Seq[CriterionLabel])
+ object RichCriterion {
+ implicit def toPhiString(x: RichCriterion): PhiString = {
+ import x._
+ phi"RichCriterion(criterion=$criterion, armIds=$armIds, labels=$labels)"
+ }
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+
+ case class Created(x: RichCriterion) 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 {
+ case class Entity(x: RichCriterion) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[RichCriterion],
+ 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: RichCriterion) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+
+ sealed trait DeleteReply
+ object DeleteReply {
+ case object Deleted extends DeleteReply
+
+ type Error = DeleteReply with DomainError
+
+ case object NotFoundError
+ extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends DeleteReply with DomainError
+ }
+}
+
+trait CriterionService {
+
+ import CriterionService._
+
+ def create(draftRichCriterion: RichCriterion)
+ (implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def getById(id: LongId[Criterion])
+ (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(origRichCriterion: RichCriterion,
+ draftRichCriterion: RichCriterion)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(id: LongId[Criterion])
+ (implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala
new file mode 100644
index 0000000..cd242c9
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala
@@ -0,0 +1,157 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object DocumentService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Can not find the document"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: Document) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Document],
+ totalFound: Int,
+ lastUpdate: Option[LocalDateTime]) extends GetListReply
+
+ type Error = GetListReply with DomainError
+
+ case object AuthorizationError
+ extends GetListReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String) extends GetListReply with DomainError
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ case class Created(x: Document) extends CreateReply
+
+ type Error = CreateReply with DomainError
+
+ case object NotFoundError
+ extends CreateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends CreateReply with DomainError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ case class Updated(updated: Document) extends UpdateReply
+
+ type Error = UpdateReply with DomainError
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+
+
+ sealed trait DeleteReply
+ object DeleteReply {
+ case object Deleted extends DeleteReply
+
+ type Error = DeleteReply with DomainError
+
+ case object NotFoundError
+ extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends DeleteReply with DomainError
+ }
+
+}
+
+trait DocumentService {
+
+ import DocumentService._
+
+
+ def getById(id: LongId[Document])
+ (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 create(draftDocument: Document)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ // Update operations are validated in internal.*Command
+ def update(orig: Document, draft: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(id: LongId[Document])
+ (implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+
+ def start(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def submit(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def restart(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def flag(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def resolve(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def unassign(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def archive(orig: Document)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala
new file mode 100644
index 0000000..30fd348
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala
@@ -0,0 +1,26 @@
+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.pdsuidomain.entities.DocumentType
+import xyz.driver.pdsuidomain.services.DocumentTypeService.GetListReply
+
+import scala.concurrent.Future
+
+object DocumentTypeService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[DocumentType], totalFound: Int) extends GetListReply
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait DocumentTypeService {
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala
new file mode 100644
index 0000000..6cde411
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala
@@ -0,0 +1,116 @@
+package xyz.driver.pdsuidomain.services
+
+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._
+
+import scala.concurrent.Future
+
+object ExtractedDataService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Extracted data hasn't been found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ case class RichExtractedData(extractedData: ExtractedData,
+ labels: List[ExtractedDataLabel])
+
+ object RichExtractedData {
+ implicit def toPhiString(x: RichExtractedData): PhiString = {
+ import x._
+ phi"RichExtractedData(extractedData=$extractedData, labels=$labels)"
+ }
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ type Error = GetByIdReply with DomainError
+ case class Entity(x: RichExtractedData) extends GetByIdReply
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetByIdReply with DomainError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[RichExtractedData], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+ case class Created(x: RichExtractedData) extends CreateReply
+
+ case object AuthorizationError
+ extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String) extends CreateReply with DomainError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+ case class Updated(updated: RichExtractedData) extends UpdateReply
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ 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 DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case object NotFoundError
+ extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends DeleteReply with DomainError
+ }
+}
+
+trait ExtractedDataService {
+
+ import ExtractedDataService._
+
+ def getById(id: LongId[ExtractedData])
+ (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 create(draftRichExtractedData: RichExtractedData)
+ (implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def update(origRichExtractedData: RichExtractedData,
+ draftRichExtractedData: RichExtractedData)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def delete(id: LongId[ExtractedData])
+ (implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala
new file mode 100644
index 0000000..64ce3ea
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.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.pdsuidomain.entities.Hypothesis
+
+import scala.concurrent.Future
+
+object HypothesisService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Hypothesis], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait HypothesisService {
+
+ import HypothesisService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala
new file mode 100644
index 0000000..d65c9f9
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala
@@ -0,0 +1,85 @@
+package xyz.driver.pdsuidomain.services
+
+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._
+
+import scala.concurrent.Future
+
+object InterventionService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Intervention not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[InterventionWithArms], totalFound: Int)
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: InterventionWithArms) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: InterventionWithArms) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+
+}
+
+trait InterventionService {
+
+ import InterventionService._
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getById(id: LongId[Intervention])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def update(origIntervention: InterventionWithArms,
+ draftIntervention: InterventionWithArms)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala
new file mode 100644
index 0000000..db7473e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.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.pdsuidomain.entities.InterventionType
+
+import scala.concurrent.Future
+
+object InterventionTypeService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[InterventionType], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait InterventionTypeService {
+
+ import InterventionTypeService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala
new file mode 100644
index 0000000..5ee69a2
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.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.pdsuidomain.entities.KeywordWithLabels
+
+import scala.concurrent.Future
+
+object KeywordService {
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[KeywordWithLabels], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait KeywordService {
+
+ import KeywordService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala
new file mode 100644
index 0000000..7b5aa66
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala
@@ -0,0 +1,137 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.error._
+import xyz.driver.pdsuidomain.entities.MedicalRecord.PdfSource
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object MedicalRecordService {
+
+ type PdfSourceFetcher = (String, String) => Future[PdfSource]
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Medical record hasn't been found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: MedicalRecord) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ 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 GetPdfSourceReply
+ object GetPdfSourceReply {
+ type Error = GetPdfSourceReply with DomainError
+
+ case class Entity(x: PdfSource.Channel) extends GetPdfSourceReply
+
+ case object AuthorizationError
+ extends GetPdfSourceReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object NotFoundError
+ extends GetPdfSourceReply with DomainError.NotFoundError {
+ def userMessage: String = "Medical record PDF hasn't been found"
+ }
+
+ case object RecordNotFoundError
+ extends GetPdfSourceReply with DomainError.NotFoundError with DefaultNotFoundError
+
+ case class CommonError(userMessage: String)
+ extends GetPdfSourceReply with DomainError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[MedicalRecord], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ case class Created(x: MedicalRecord) extends CreateReply
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: MedicalRecord) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+
+ case class Settings(pdfSourceBucket: String)
+}
+
+trait MedicalRecordService {
+
+ import MedicalRecordService._
+
+ def getById(recordId: LongId[MedicalRecord])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getPdfSource(recordId: LongId[MedicalRecord])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetPdfSourceReply]
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def create(draft: MedicalRecord): Future[CreateReply]
+
+ def update(origRecord: MedicalRecord,
+ draftRecord: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def start(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def submit(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def restart(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def flag(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def resolve(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def unassign(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def archive(orig: MedicalRecord)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala
new file mode 100644
index 0000000..ff95ed0
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala
@@ -0,0 +1,126 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object PatientCriterionService {
+
+ case class DraftPatientCriterion(id: LongId[PatientCriterion],
+ eligibilityStatus: Option[FuzzyValue],
+ isVerified: Option[Boolean]) {
+ def applyTo(orig: PatientCriterion) = {
+ orig.copy(
+ eligibilityStatus = eligibilityStatus.orElse(orig.eligibilityStatus),
+ isVerified = isVerified.getOrElse(orig.isVerified)
+ )
+ }
+ }
+
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Patient criterion not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ type Error = GetListReply with DomainError
+
+ case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[Arm], Boolean)],
+ totalFound: Int,
+ lastUpdate: Option[LocalDateTime]) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends GetListReply with DomainError.NotFoundError with DefaultPatientNotFoundError
+
+ case class CommonError(userMessage: String)
+ extends GetListReply with DomainError
+
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ type Error = GetByIdReply with DomainError
+
+ case class Entity(x: PatientCriterion,
+ labelId: LongId[Label],
+ armList: List[Arm],
+ criterionIsCompound: Boolean) extends GetByIdReply
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object NotFoundError
+ extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError
+
+ case object PatientNotFoundError
+ extends GetByIdReply with DomainError.NotFoundError with DefaultPatientNotFoundError
+
+ case class CommonError(userMessage: String)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x, labelId, armList, criterionIsCompound) =>
+ phi"GetByIdReply.Entity(entity=$x, labelId=$labelId, " +
+ phi"armList=$armList, criterionIsCompound=$criterionIsCompound)"
+ }
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case object Updated extends UpdateReply
+
+ case object AuthorizationError
+ extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends UpdateReply with DomainError.NotFoundError with DefaultPatientNotFoundError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+ }
+}
+
+trait PatientCriterionService {
+
+ import PatientCriterionService._
+
+ def getAll(patientId: UuidId[Patient],
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getById(patientId: UuidId[Patient],
+ id: LongId[PatientCriterion])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def updateList(patientId: UuidId[Patient],
+ draftEntities: List[DraftPatientCriterion])
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def update(origEntity: PatientCriterion,
+ draftEntity: PatientCriterion,
+ patientId: UuidId[Patient])
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala
new file mode 100644
index 0000000..64b4b81
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala
@@ -0,0 +1,137 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Arm, Trial, _}
+
+import scala.concurrent.Future
+
+object PatientEligibleTrialService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Patient eligible trial not found"
+ }
+
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ case class RichPatientEligibleTrial(trial: Trial,
+ group: PatientTrialArmGroupView,
+ arms: List[Arm])
+ object RichPatientEligibleTrial {
+ implicit def toPhiString(x: RichPatientEligibleTrial): PhiString = {
+ phi"RichPatientEligibleTrial(group=${x.group}, trial=${x.trial}, arms=${x.arms})"
+ }
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[RichPatientEligibleTrial], totalFound: Int)
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetListReply with DomainError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: RichPatientEligibleTrial) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends GetByIdReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String) extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait GetCriterionListOfGroupReply
+ object GetCriterionListOfGroupReply {
+ case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[Arm], Boolean)], totalFound: Int)
+ extends GetCriterionListOfGroupReply
+
+ type Error = GetCriterionListOfGroupReply with DomainError
+
+ case object AuthorizationError
+ extends GetCriterionListOfGroupReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object NotFoundError
+ extends GetCriterionListOfGroupReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends GetCriterionListOfGroupReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetCriterionListOfGroupReply with DomainError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: RichPatientEligibleTrial) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends UpdateReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+}
+
+trait PatientEligibleTrialService {
+
+ import PatientEligibleTrialService._
+
+ def getAll(patientId: UuidId[Patient],
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getById(patientId: UuidId[Patient],
+ id: LongId[PatientTrialArmGroup])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getCriterionListByGroupId(patientId: UuidId[Patient],
+ id: LongId[PatientTrialArmGroup])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetCriterionListOfGroupReply]
+
+ def update(origEligibleTrialWithTrial: RichPatientEligibleTrial,
+ draftPatientTrialArmGroup: PatientTrialArmGroupView)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala
new file mode 100644
index 0000000..dff595a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala
@@ -0,0 +1,105 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.domain.UuidId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Hypothesis, Patient, PatientHypothesis}
+
+import scala.concurrent.Future
+
+object PatientHypothesisService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Patient hypothesis not found"
+ }
+
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[PatientHypothesis], totalFound: Int)
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetListReply with DomainError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: PatientHypothesis) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends GetByIdReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String) extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: PatientHypothesis) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends UpdateReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+}
+
+trait PatientHypothesisService {
+
+ import PatientHypothesisService._
+
+ def getAll(patientId: UuidId[Patient],
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getById(patientId: UuidId[Patient],
+ hypothesisId: UuidId[Hypothesis])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def update(origPatientHypothesis: PatientHypothesis,
+ draftPatientHypothesis: PatientHypothesis)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala
new file mode 100644
index 0000000..8e791d4
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala
@@ -0,0 +1,70 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDate
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object PatientLabelEvidenceService {
+
+ case class Aggregated(evidence: PatientLabelEvidence,
+ date: LocalDate,
+ documentType: String,
+ providerType: String)
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: Aggregated) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case class NotFoundError(userMessage: String) extends GetByIdReply
+ with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetByIdReply
+ with DomainError
+
+ case object AuthorizationError extends GetByIdReply
+ with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Aggregated], totalFound: Int)
+ extends GetListReply
+
+ type Error = GetListReply with DomainError
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String) extends GetListReply
+ with DomainError
+ }
+}
+
+trait PatientLabelEvidenceService {
+
+ import PatientLabelEvidenceService._
+
+ def getById(patientId: UuidId[Patient],
+ labelId: LongId[Label],
+ id: LongId[PatientLabelEvidence])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getAll(patientId: UuidId[Patient],
+ labelId: LongId[Label],
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala
new file mode 100644
index 0000000..1d488ad
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala
@@ -0,0 +1,124 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object PatientLabelService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Patient label not found"
+ }
+
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[(PatientLabel, Boolean)], totalFound: Int)
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetListReply with DomainError
+ }
+
+ sealed trait GetDefiningCriteriaListReply
+ object GetDefiningCriteriaListReply {
+ case class EntityList(xs: Seq[PatientLabel], totalFound: Int)
+ extends GetDefiningCriteriaListReply
+
+ case object AuthorizationError
+ extends GetDefiningCriteriaListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object PatientNotFoundError
+ extends GetDefiningCriteriaListReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case class CommonError(userMessage: String) extends GetDefiningCriteriaListReply with DomainError
+ }
+
+ sealed trait GetByLabelIdReply
+ object GetByLabelIdReply {
+ case class Entity(x: PatientLabel, isVerified: Boolean) extends GetByLabelIdReply
+
+ type Error = GetByLabelIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByLabelIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends GetByLabelIdReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByLabelIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String) extends GetByLabelIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByLabelIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x, y) => phi"GetByIdReply.Entity($x, $y)"
+ }
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: PatientLabel, isVerified: Boolean) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object PatientNotFoundError
+ extends UpdateReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x, y) => phi"Updated($x, $y)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+}
+
+trait PatientLabelService {
+
+ import PatientLabelService._
+
+ def getAll(patientId: UuidId[Patient],
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def getDefiningCriteriaList(patientId: UuidId[Patient],
+ hypothesisId: UuidId[Hypothesis],
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetDefiningCriteriaListReply]
+
+ def getByLabelIdOfPatient(patientId: UuidId[Patient],
+ labelId: LongId[Label])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByLabelIdReply]
+
+ def update(origPatientLabel: PatientLabel,
+ draftPatientLabel: PatientLabel)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala
new file mode 100644
index 0000000..3f8b606
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala
@@ -0,0 +1,105 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities._
+
+import scala.concurrent.Future
+
+object PatientService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Patient], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: Patient) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: Patient) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+}
+
+trait PatientService {
+
+ import PatientService._
+
+ def getById(id: UuidId[Patient])
+ (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 unassign(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def start(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def submit(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def restart(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def flag(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def resolve(origPatient: Patient)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala
new file mode 100644
index 0000000..49901a4
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala
@@ -0,0 +1,27 @@
+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.pdsuidomain.entities.ProviderType
+
+import scala.concurrent.Future
+
+object ProviderTypeService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[ProviderType], totalFound: Int) extends GetListReply
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait ProviderTypeService {
+
+ import ProviderTypeService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala
new file mode 100644
index 0000000..4417a54
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala
@@ -0,0 +1,54 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuidomain.entities.ScrapedTrial
+
+import scala.concurrent.Future
+
+object ScrapedTrialsService {
+
+ sealed trait GetRawTrialReply
+ object GetRawTrialReply {
+ type Error = GetRawTrialReply with DomainError
+
+ case class TrialRawEntity(rawTrial: ScrapedTrial) extends GetRawTrialReply
+
+ case object NotFoundError extends GetRawTrialReply with DomainError.NotFoundError {
+ override def userMessage: String = "Raw clinical trial not found"
+ }
+ }
+
+ sealed trait GetRawTrialOptReply
+ object GetRawTrialOptReply {
+ case class TrialRawEntity(rawTrial: Option[ScrapedTrial]) extends GetRawTrialOptReply
+ }
+
+ sealed trait GetAllRawTrialsExceptReply
+ object GetAllRawTrialsExceptReply {
+ case class MultipleRawTrials(rawTrials: Seq[ScrapedTrial])
+ extends GetAllRawTrialsExceptReply
+ }
+
+ sealed trait GetHtmlForReply
+ object GetHtmlForReply {
+ type TrialHtmlMap = Map[String, String]
+
+ /**
+ * @param trialHtmlMap nctId -> html
+ */
+ case class HtmlMap(trialHtmlMap: TrialHtmlMap) extends GetHtmlForReply
+ }
+}
+
+trait ScrapedTrialsService {
+
+ import ScrapedTrialsService._
+
+ def getRawTrial(nctId: String): Future[GetRawTrialReply]
+
+ def getRawTrialOpt(nctId: String): Future[GetRawTrialOptReply]
+
+ def getAllRawTrialsExcept(nctIds: Seq[String], limit: Int): Future[GetAllRawTrialsExceptReply]
+
+ def getHtmlFor(nctIds: Set[String]): Future[GetHtmlForReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala
new file mode 100644
index 0000000..d086c5f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.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.pdsuidomain.entities.StudyDesign
+
+import scala.concurrent.Future
+
+object StudyDesignService {
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[StudyDesign], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait StudyDesignService {
+
+ import StudyDesignService._
+
+ def getAll(sorting: Option[Sorting] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala
new file mode 100644
index 0000000..4af4449
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala
@@ -0,0 +1,132 @@
+package xyz.driver.pdsuidomain.services
+
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db._
+import xyz.driver.pdsuicommon.domain.StringId
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Trial
+import xyz.driver.pdsuidomain.entities.Trial.PdfSource
+
+import scala.concurrent.Future
+
+object TrialService {
+
+ trait DefaultNotFoundError {
+ def userMessage: String = "Trial not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[Trial], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ case class Entity(x: Trial) extends GetByIdReply
+
+ type Error = GetByIdReply with DomainError
+
+ case object NotFoundError
+ extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
+ extends GetByIdReply with DomainError
+
+ implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x) => phi"GetByIdReply.Entity($x)"
+ }
+ }
+
+ sealed trait GetPdfSourceReply
+ object GetPdfSourceReply {
+ type Error = GetPdfSourceReply with DomainError
+
+ case class Entity(x: PdfSource) extends GetPdfSourceReply
+
+ case object AuthorizationError
+ extends GetPdfSourceReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+
+ case object NotFoundError
+ extends GetPdfSourceReply with DomainError.NotFoundError {
+ def userMessage: String = "Trial's PDF hasn't been found"
+ }
+
+ case object TrialNotFoundError
+ extends GetPdfSourceReply with DomainError.NotFoundError with DefaultNotFoundError
+
+ case class CommonError(userMessage: String)
+ extends GetPdfSourceReply with DomainError
+ }
+
+ sealed trait UpdateReply
+ object UpdateReply {
+ type Error = UpdateReply with DomainError
+
+ case class Updated(updated: Trial) extends UpdateReply
+
+ case object NotFoundError
+ extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError
+
+ case object AuthorizationError
+ extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
+
+ case class CommonError(userMessage: String)
+ extends UpdateReply with DomainError
+
+ implicit def toPhiString(reply: UpdateReply): PhiString = reply match {
+ case Updated(x) => phi"Updated($x)"
+ case x: Error => DomainError.toPhiString(x)
+ }
+ }
+}
+
+trait TrialService {
+
+ import TrialService._
+
+ def getById(id: StringId[Trial])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getPdfSource(trialId: StringId[Trial])
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetPdfSourceReply]
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def update(origTrial: Trial, draftTrial: Trial)
+ (implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def start(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def submit(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def restart(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def flag(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def resolve(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def archive(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def unassign(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
+
+ def removeTrialDetails(trialId: StringId[Trial]): Unit
+}