aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuidomain/formats
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuidomain/formats')
-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
52 files changed, 1997 insertions, 0 deletions
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"
+ }
+}