aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.sbt36
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala25
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala5
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala167
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala1
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala1
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala16
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala39
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabelEvidenceView.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala5
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/UserHistory.scala156
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala30
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala26
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala27
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala15
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala32
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala52
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala8
-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.scala28
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala33
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala6
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala11
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala1
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala20
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/userhistory/ApiUserHistory.scala43
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala64
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala62
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala39
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala7
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala8
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala19
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala78
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala31
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala13
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala45
-rw-r--r--src/test/scala/xyz/driver/pdsuicommon/utils/StringOpsSuite.scala33
-rw-r--r--src/test/scala/xyz/driver/pdsuidomain/DocumentSuite.scala31
63 files changed, 1232 insertions, 184 deletions
diff --git a/build.sbt b/build.sbt
index 34e6334..a91c093 100644
--- a/build.sbt
+++ b/build.sbt
@@ -5,21 +5,23 @@ lazy val core = (project in file("."))
.driverLibrary("pds-ui-common")
.settings(scalastyleSettings ++ /* wartRemoverSettings ++ */ formatSettings)
.settings(libraryDependencies ++= Seq(
- "ch.qos.logback" % "logback-classic" % "1.1.7",
- "org.slf4j" % "slf4j-api" % "1.7.21",
- "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0",
- "com.typesafe" % "config" % "1.3.0",
- "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.3",
- "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4",
- "com.typesafe.play" %% "play" % "2.5.15",
- "org.davidbild" %% "tristate-core" % "0.2.0",
- "org.davidbild" %% "tristate-play" % "0.2.0" exclude ("com.typesafe.play", "play-json"),
- "org.asynchttpclient" % "async-http-client" % "2.0.24",
- "io.getquill" %% "quill-jdbc" % "1.2.1",
- "io.github.cloudify" %% "spdf" % "1.4.0",
- "de.svenkubiak" % "jBCrypt" % "0.4.1",
- "com.google.cloud" % "google-cloud-storage" % "0.9.4-beta",
- "com.github.pureconfig" %% "pureconfig" % "0.7.2",
- "ai.x" %% "diff" % "1.2.0-get-simple-name-fix" % "test",
- "org.scalatest" %% "scalatest" % "3.0.0" % "test"
+ "ch.qos.logback" % "logback-classic" % "1.1.7",
+ "org.slf4j" % "slf4j-api" % "1.7.21",
+ "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0",
+ "com.typesafe" % "config" % "1.3.0",
+ "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.3",
+ "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4",
+ "com.typesafe.play" %% "play" % "2.5.15",
+ "org.davidbild" %% "tristate-core" % "0.2.0",
+ "org.davidbild" %% "tristate-play" % "0.2.0" exclude ("com.typesafe.play", "play-json"),
+ "org.asynchttpclient" % "async-http-client" % "2.0.24",
+ "io.getquill" %% "quill-jdbc" % "1.2.1",
+ "io.github.cloudify" %% "spdf" % "1.4.0",
+ "com.sendgrid" % "sendgrid-java" % "3.1.0" exclude ("org.mockito", "mockito-core"),
+ "com.github.spullara.mustache.java" % "scala-extensions-2.11" % "0.9.4",
+ "de.svenkubiak" % "jBCrypt" % "0.4.1",
+ "com.google.cloud" % "google-cloud-storage" % "0.9.4-beta",
+ "com.github.pureconfig" %% "pureconfig" % "0.7.2",
+ "ai.x" %% "diff" % "1.2.0-get-simple-name-fix" % "test",
+ "org.scalatest" %% "scalatest" % "3.0.0" % "test"
))
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
index bab29d5..0bc8220 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
@@ -11,8 +11,7 @@ object SafeBridgeUploadQueue {
trait Tag extends Product with Serializable
- case class SafeTask[T <: Tag](tag: T,
- private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item)
+ case class SafeTask[T <: Tag](tag: T, private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item)
object SafeTask {
implicit def toPhiString[T <: Tag](x: SafeTask[T]): PhiString = {
@@ -27,20 +26,20 @@ object SafeBridgeUploadQueue {
}
-class SafeBridgeUploadQueue[T <: Tag](kind: String,
- origQueue: BridgeUploadQueue)
- (implicit
- tagMarshaller: Marshaller[T, String],
- dependencyResolver: DependencyResolver[T],
- executionContext: ExecutionContext) {
+class SafeBridgeUploadQueue[T <: Tag](kind: String, origQueue: BridgeUploadQueue)(
+ implicit tagMarshaller: Marshaller[T, String],
+ dependencyResolver: DependencyResolver[T],
+ executionContext: ExecutionContext) {
type Task = SafeTask[T]
- def add(tag: T): Future[BridgeUploadQueue.Item] = origQueue.add(BridgeUploadQueue.Item(
- kind = kind,
- tag = tagMarshaller.write(tag),
- dependency = dependencyResolver.getDependency(tag)
- ))
+ def add(tag: T): Future[BridgeUploadQueue.Item] =
+ origQueue.add(
+ BridgeUploadQueue.Item(
+ kind = kind,
+ tag = tagMarshaller.write(tag),
+ dependency = dependencyResolver.getDependency(tag)
+ ))
def tryRetry(task: Task): Future[Option[Task]] = wrap(origQueue.tryRetry(task.queueItem))
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala b/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala
index e5a628c..ac42a34 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/FakeDbIo.scala
@@ -3,7 +3,7 @@ package xyz.driver.pdsuicommon.db
import scala.concurrent.Future
object FakeDbIo extends DbIo {
- override def runAsync[T](f: => T): Future[T] = Future.successful(f)
+ override def runAsync[T](f: => T): Future[T] = Future.successful(f)
override def runAsyncTx[T](f: => T): Future[T] = Future.successful(f)
- override def runSyncTx[T](f: => T): Unit = f
+ override def runSyncTx[T](f: => T): Unit = f
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
index 768d1e3..f804e87 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
@@ -45,8 +45,7 @@ object MySqlContext extends PhiLogging {
}
class MySqlContext(dataSource: DataSource with Closeable, settings: Settings)
- extends MysqlJdbcContext[MysqlEscape](dataSource)
- with TransactionalContext
+ extends MysqlJdbcContext[MysqlEscape](dataSource) with TransactionalContext
with EntityExtractorDerivation[Literal] {
private val tpe = Executors.newFixedThreadPool(settings.threadPoolSize)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
index 223c66e..9800903 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala
@@ -5,6 +5,7 @@ import java.net.URI
import play.api.libs.functional.syntax._
import play.api.libs.json._
import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuidomain.entities.CaseId
object Serialization {
@@ -44,4 +45,8 @@ object Serialization {
private val passwordHashJsonWrites: Writes[PasswordHash] = Writes(
passwordHash => JsString(passwordHash.value.toString))
implicit val passwordHashJsonFormat: Format[PasswordHash] = Format(passwordHashJsonReads, passwordHashJsonWrites)
+
+ private val caseIdJsonReads: Reads[CaseId] = Reads.StringReads.map(CaseId(_))
+ private val caseIdJsonWrites: Writes[CaseId] = Writes(caseId => JsString(caseId.id))
+ implicit val caseIdJsonFormat: Format[CaseId] = Format(caseIdJsonReads, caseIdJsonWrites)
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala
index 42bf92d..cee9c73 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/CharOps.scala
@@ -20,7 +20,7 @@ private object CharOps {
"\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000"
private val Multiplier: Int = 1682554634
- private val Shift: Int = Integer.numberOfLeadingZeros(Table.length - 1)
+ private val Shift: Int = Integer.numberOfLeadingZeros(Table.length - 1)
def matches(c: Char): Boolean = Table.charAt((Multiplier * c) >>> Shift) == c
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala
index b38721e..eaac761 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/StringOps.scala
@@ -11,7 +11,7 @@ final class StringOps(val self: String) extends AnyVal {
""
} else {
val start = self.indexWhere(shouldKeep)
- val end = self.lastIndexWhere(shouldKeep)
+ val end = self.lastIndexWhere(shouldKeep)
if (start >= 0 && end >= 0) {
self.substring(start, end + 1)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala
index 2c66a23..6c04dfa 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/WriteableImplicits.scala
@@ -8,7 +8,9 @@ trait WriteableImplicits {
// Write JSON by default at now
implicit def defaultWriteable[T](implicit inner: Writes[T]) = Writeable[T](
- { x: T => Writeable.writeableOf_JsValue.transform(Json.toJson(x)) },
+ { x: T =>
+ Writeable.writeableOf_JsValue.transform(Json.toJson(x))
+ },
Option(ContentTypes.JSON)
)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala
index fa05e96..3476a1e 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/utils/WritesUtils.scala
@@ -14,7 +14,7 @@ object WritesUtils {
w.transform { input: JsValue =>
input match {
case JsObject(map) => JsObject(map.filter(Function.tupled(p)))
- case x => x
+ case x => x
}
}
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
index 5af00bc..5c957fb 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
@@ -1,6 +1,6 @@
package xyz.driver.pdsuidomain.entities
-import java.time.{LocalDate, LocalDateTime}
+import java.time.{LocalDate, LocalDateTime, ZoneId}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.core.{JsonGenerator, JsonParser}
@@ -17,6 +17,72 @@ import xyz.driver.pdsuicommon.compat.Implicits._
final case class ProviderType(id: LongId[ProviderType], name: String)
object ProviderType {
+ sealed trait ProviderTypeName
+ case object MedicalOncology extends ProviderTypeName
+ case object Surgery extends ProviderTypeName
+ case object Pathology extends ProviderTypeName
+ case object MolecularPathology extends ProviderTypeName
+ case object LaboratoryMedicine extends ProviderTypeName
+ case object Radiology extends ProviderTypeName
+ case object InterventionalRadiology extends ProviderTypeName
+ case object RadiationOncology extends ProviderTypeName
+ case object PrimaryCare extends ProviderTypeName
+ case object Cardiology extends ProviderTypeName
+ case object Dermatology extends ProviderTypeName
+ case object Ophthalmology extends ProviderTypeName
+ case object Gastroenterology extends ProviderTypeName
+ case object Neurology extends ProviderTypeName
+ case object Psychiatry extends ProviderTypeName
+ case object Gynecology extends ProviderTypeName
+ case object InfectiousDisease extends ProviderTypeName
+ case object Immunology extends ProviderTypeName
+ case object Nephrology extends ProviderTypeName
+ case object Rheumatology extends ProviderTypeName
+ case object Cytology extends ProviderTypeName
+ case object Otolaryngology extends ProviderTypeName
+ case object Anesthesiology extends ProviderTypeName
+ case object Urology extends ProviderTypeName
+ case object PalliativeCare extends ProviderTypeName
+ case object EmergencyMedicine extends ProviderTypeName
+ case object SocialWork extends ProviderTypeName
+ case object NA extends ProviderTypeName
+ case object Other extends ProviderTypeName
+
+ def fromString(txt: String): Option[ProviderTypeName] = {
+ txt match {
+ case "Medical Oncology" => Some(MedicalOncology)
+ case "Surgery" => Some(Surgery)
+ case "Pathology" => Some(Pathology)
+ case "Molecular Pathology" => Some(MolecularPathology)
+ case "LaboratoryMedicine" => Some(LaboratoryMedicine)
+ case "Radiology" => Some(Radiology)
+ case "Interventional Radiology" => Some(InterventionalRadiology)
+ case "Radiation Oncology" => Some(RadiationOncology)
+ case "Primary Care" => Some(PrimaryCare)
+ case "Cardiology" => Some(Cardiology)
+ case "Dermatology" => Some(Dermatology)
+ case "Ophthalmology" => Some(Ophthalmology)
+ case "Gastroenterology" => Some(Gastroenterology)
+ case "Neurology" => Some(Neurology)
+ case "Psychiatry" => Some(Psychiatry)
+ case "Gynecology" => Some(Gynecology)
+ case "Infectious Disease" => Some(InfectiousDisease)
+ case "Immunology" => Some(Immunology)
+ case "Nephrology" => Some(Nephrology)
+ case "Rheumatology" => Some(Rheumatology)
+ case "Cytology" => Some(Cytology)
+ case "Otolaryngology" => Some(Otolaryngology)
+ case "Anesthesiology" => Some(Anesthesiology)
+ case "Urology" => Some(Urology)
+ case "Palliative Care" => Some(PalliativeCare)
+ case "Emergency Medicine" => Some(EmergencyMedicine)
+ case "Social Work" => Some(SocialWork)
+ case "N/A" => Some(NA)
+ case "Other" => Some(Other)
+ case _ => None
+ }
+ }
+
implicit def toPhiString(x: ProviderType): PhiString = {
import x._
phi"ProviderType(id=$id, category=${Unsafe(name)})"
@@ -26,6 +92,38 @@ object ProviderType {
final case class DocumentType(id: LongId[DocumentType], name: String)
object DocumentType {
+ sealed trait DocumentTypeName
+ case object OutpatientPhysicianNote extends DocumentTypeName
+ case object DischargeNote extends DocumentTypeName
+ case object LaboratoryReport extends DocumentTypeName
+ case object MedicationList extends DocumentTypeName
+ case object HospitalizationNote extends DocumentTypeName
+ case object PathologyReport extends DocumentTypeName
+ case object RadiologyReport extends DocumentTypeName
+ case object OperativeProcedureReport extends DocumentTypeName
+ case object MedicationAdministration extends DocumentTypeName
+ case object SocialWorkCaseManagementNote extends DocumentTypeName
+ case object NonPhysicianProviderNote extends DocumentTypeName
+ case object Administrative extends DocumentTypeName
+
+ def fromString(txt: String): Option[DocumentTypeName] = {
+ txt match {
+ case "Outpatient Physician Note" => Some(OutpatientPhysicianNote)
+ case "Discharge Note" => Some(DischargeNote)
+ case "Laboratory Report" => Some(LaboratoryReport)
+ case "Medication List" => Some(MedicationList)
+ case "Hospitalization Note" => Some(HospitalizationNote)
+ case "Pathology Report" => Some(PathologyReport)
+ case "Radiology Report" => Some(RadiologyReport)
+ case "Operative/Procedure Report" => Some(OperativeProcedureReport)
+ case "Medication Administration" => Some(MedicationAdministration)
+ case "Social Work/Case Management Note" => Some(SocialWorkCaseManagementNote)
+ case "Non-physician Provider Note" => Some(NonPhysicianProviderNote)
+ case "Administrative" => Some(Administrative)
+ case _ => None
+ }
+ }
+
implicit def toPhiString(x: DocumentType): PhiString = {
import x._
phi"DocumentType(id=$id, name=${Unsafe(name)})"
@@ -71,6 +169,7 @@ object Document {
def oneOf(xs: Set[Status]): Boolean = xs.contains(this)
}
+
object Status {
case object New extends Status
case object Organized extends Status
@@ -82,12 +181,58 @@ object Document {
val All = Set[Status](New, Organized, Extracted, Done, Flagged, Archived)
val AllPrevious = Set[Status](Organized, Extracted)
+ val fromString: 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"
+ }
+
implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
}
+ sealed trait RequiredType extends Product with Serializable {
+
+ def oneOf(xs: RequiredType*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[RequiredType]): Boolean = xs.contains(this)
+
+ }
+
+ object RequiredType {
+ case object OPN extends RequiredType
+ case object PN extends RequiredType
+
+ val All = Set[RequiredType](OPN, PN)
+
+ val fromString: PartialFunction[String, RequiredType] = {
+ case "OPN" => RequiredType.OPN
+ case "PN" => RequiredType.PN
+ }
+
+ def requiredTypeToString(x: RequiredType): String = x match {
+ case RequiredType.OPN => "OPN"
+ case RequiredType.PN => "PN"
+ }
+
+ implicit def toPhiString(x: RequiredType): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
implicit def toPhiString(x: Document): PhiString = {
import x._
- phi"Document(id=$id, status=$status, assignee=$assignee, previousAssignee=$previousAssignee, recordId=$recordId)"
+ phi"Document(id=$id, status=$status, assignee=$assignee, " +
+ phi"previousAssignee=$previousAssignee, lastActiveUserId=$lastActiveUserId, recordId=$recordId)"
}
val validator: Validator[Document, Document] = { input =>
@@ -126,11 +271,13 @@ final case class Document(id: LongId[Document] = LongId(0L),
previousStatus: Option[Document.Status],
assignee: Option[LongId[User]],
previousAssignee: Option[LongId[User]],
+ lastActiveUserId: Option[LongId[User]],
recordId: LongId[MedicalRecord],
physician: Option[String],
typeId: Option[LongId[DocumentType]], // not null
providerName: Option[String], // not null
providerTypeId: Option[LongId[ProviderType]], // not null
+ requiredType: Option[Document.RequiredType],
meta: Option[TextJson[Meta]], // not null
startDate: Option[LocalDate], // not null
endDate: Option[LocalDate],
@@ -142,6 +289,22 @@ final case class Document(id: LongId[Document] = LongId(0L),
assert(AllPrevious.contains(previousStatus.get), s"Previous status has invalid value: ${previousStatus.get}")
}
+ def getRequiredType(documentTypeName: String, providerTypeName: String): Option[Document.RequiredType] = {
+ import DocumentType.{OutpatientPhysicianNote, PathologyReport}
+ import ProviderType.MedicalOncology
+
+ (DocumentType.fromString(documentTypeName), ProviderType.fromString(providerTypeName), startDate) match {
+ case (Some(OutpatientPhysicianNote), Some(MedicalOncology), Some(date))
+ if !(date.isAfter(LocalDate.now(ZoneId.of("Z"))) || date.isBefore(
+ LocalDate.now(ZoneId.of("Z")).minusMonths(6))) =>
+ Some(Document.RequiredType.OPN)
+
+ case (Some(PathologyReport), _, _) => Some(Document.RequiredType.PN)
+
+ case _ => None
+ }
+ }
+
// TODO: with the current business logic code this constraint sometimes harmful
// require(status match {
// case Document.Status.New if assignee.isDefined => false
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala
index 9972142..32258dc 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/ExtractedData.scala
@@ -19,7 +19,6 @@ object ExtractedData {
final case class Meta(keyword: Meta.Keyword, evidence: Meta.Evidence)
object Meta {
-
final case class Evidence(pageRatio: Double, start: TextLayerPosition, end: TextLayerPosition)
final case class TextLayerPosition(page: Integer, index: Integer, offset: Integer)
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala
index dc82050..e691547 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala
@@ -28,6 +28,7 @@ final case class Intervention(id: LongId[Intervention],
typeId: Option[LongId[InterventionType]],
originalType: Option[String],
description: String,
+ originalDescription: String,
isActive: Boolean)
object Intervention {
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala
new file mode 100644
index 0000000..e82bcc3
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala
@@ -0,0 +1,16 @@
+package xyz.driver.pdsuidomain.entities
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+
+object LinkedPatient {
+
+ implicit def toPhiString(x: LinkedPatient): PhiString = {
+ import x._
+ phi"LinkedPatient(userId=$userId, patientId=$patientId, trialId=$trialId)"
+ }
+}
+
+case class LinkedPatient(userId: LongId[User],
+ patientId: UuidId[Patient],
+ trialId: StringId[Trial])
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala
index 17de7de..0e30fa0 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala
@@ -134,7 +134,8 @@ object MedicalRecord {
implicit def toPhiString(x: MedicalRecord): PhiString = {
import x._
- phi"MedicalRecord(id=$id, status=$status, assignee=$assignee, previousAssignee=$previousAssignee)"
+ phi"MedicalRecord(id=$id, status=$status, assignee=$assignee, " +
+ phi"previousAssignee=$previousAssignee, lastActiveUserId=$lastActiveUserId)"
}
}
@@ -143,6 +144,7 @@ case class MedicalRecord(id: LongId[MedicalRecord],
previousStatus: Option[MedicalRecord.Status],
assignee: Option[LongId[User]],
previousAssignee: Option[LongId[User]],
+ lastActiveUserId: Option[LongId[User]],
patientId: UuidId[Patient],
requestId: RecordRequestId,
disease: String,
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
index 93262a4..695245c 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Patient.scala
@@ -45,7 +45,7 @@ object Patient {
implicit def toPhiString(x: Patient): PhiString = {
import x._
- phi"Patient(id=$id, status=$status, previousStatus=$previousStatus, " +
+ phi"Patient(id=$id, status=$status, previousStatus=$previousStatus, lastActiveUserId=$lastActiveUserId" +
phi"assignee=$assignee, previousAssignee=$previousAssignee)"
}
}
@@ -57,6 +57,7 @@ final case class Patient(id: UuidId[Patient],
assignee: Option[LongId[User]],
previousStatus: Option[Patient.Status],
previousAssignee: Option[LongId[User]],
+ lastActiveUserId: Option[LongId[User]],
isUpdateRequired: Boolean,
condition: String,
orderId: PatientOrderId,
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala
index 8084884..e16c324 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientEligibleTrial.scala
@@ -82,35 +82,8 @@ object PatientTrialArmGroup {
implicit def toPhiString(x: PatientTrialArmGroup): PhiString = {
import x._
phi"PatientTrialArmGroup(id=$id, eligibleTrialId=$eligibleTrialId, " +
- phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, isVerified=$isVerified)"
- }
-
- /**
- * @see https://driverinc.atlassian.net/wiki/display/DMPD/EV+Business+Process
- */
- def getEligibilityStatusForEvToRc(criterionList: List[(Option[Boolean], Option[FuzzyValue])]): Option[FuzzyValue] = {
- def isEligible = {
- // Eligible, if for all (verified for EV) label-criteria eligibilityStatus=NULL or YES and at least one has status=YES
- // If method executes from PatientService (when EV submit patient) need to check value PatientCriterion.isVerified
- val verifiedList = criterionList.filter { case (isVerifiedOpt, _) => isVerifiedOpt.isEmpty || isVerifiedOpt.get }
- verifiedList.forall {
- case (_, eligibilityStatus) =>
- eligibilityStatus.isEmpty || eligibilityStatus.contains(FuzzyValue.Yes)
- } && verifiedList.exists { case (_, eligibilityStatus) => eligibilityStatus.contains(FuzzyValue.Yes) }
- }
-
- if (criterionList.exists {
- case (isVerified, eligibilityStatus) =>
- eligibilityStatus.contains(FuzzyValue.No) && (isVerified.isEmpty || isVerified.get)
- }) { Some(FuzzyValue.No) } else if (criterionList.forall {
- case (_, eligibilityStatus) => eligibilityStatus.isEmpty
- }) {
- None
- } else if (isEligible) {
- Some(FuzzyValue.Yes)
- } else {
- Some(FuzzyValue.Maybe)
- }
+ phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, " +
+ phi"verifiedEligibilityStatus=${Unsafe(verifiedEligibilityStatus)}, isVerified=$isVerified)"
}
/**
@@ -131,14 +104,16 @@ object PatientTrialArmGroup {
final case class PatientTrialArmGroup(id: LongId[PatientTrialArmGroup],
eligibleTrialId: UuidId[PatientEligibleTrial],
eligibilityStatus: Option[FuzzyValue],
+ verifiedEligibilityStatus: Option[FuzzyValue],
isVerified: Boolean)
object PatientTrialArmGroupView {
implicit def toPhiString(x: PatientTrialArmGroupView): PhiString = {
import x._
- phi"PatientTrialArmGroupView(id=$id, patientId=$patientId, trialId=$trialId, hypothesisId=$hypothesisId, " +
- phi"eligibilityStatus=${Unsafe(eligibilityStatus)}, isVerified=$isVerified)"
+ phi"PatientTrialArmGroupView(id=$id, patientId=$patientId, trialId=$trialId, " +
+ phi"hypothesisId=$hypothesisId, eligibilityStatus=${Unsafe(eligibilityStatus)}, " +
+ phi"verifiedEligibilityStatus=${Unsafe(verifiedEligibilityStatus)}, isVerified=$isVerified)"
}
}
@@ -147,11 +122,13 @@ final case class PatientTrialArmGroupView(id: LongId[PatientTrialArmGroup],
trialId: StringId[Trial],
hypothesisId: UuidId[Hypothesis],
eligibilityStatus: Option[FuzzyValue],
+ verifiedEligibilityStatus: Option[FuzzyValue],
isVerified: Boolean) {
def applyTo(trialArmGroup: PatientTrialArmGroup) = {
trialArmGroup.copy(
eligibilityStatus = this.eligibilityStatus,
+ verifiedEligibilityStatus = this.verifiedEligibilityStatus,
isVerified = this.isVerified
)
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabelEvidenceView.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabelEvidenceView.scala
new file mode 100644
index 0000000..60298b9
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/PatientLabelEvidenceView.scala
@@ -0,0 +1,28 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.LocalDate
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+
+object PatientLabelEvidenceView {
+ implicit def toPhiString(x: PatientLabelEvidenceView): PhiString = {
+ import x._
+ phi"PatientLabelEvidenceViewRepository.Row(id=$id, value=$value, documentId=$documentId, " +
+ phi"evidenceId=$evidenceId, reportId=$reportId, patientId=$patientId, labelId=$labelId, " +
+ phi"isImplicitMatch=$isImplicitMatch)"
+ }
+}
+
+final case class PatientLabelEvidenceView(id: LongId[PatientLabelEvidence],
+ value: FuzzyValue,
+ evidenceText: String,
+ documentId: Option[LongId[Document]],
+ evidenceId: Option[LongId[ExtractedData]],
+ reportId: Option[UuidId[DirectReport]],
+ documentType: String,
+ date: Option[LocalDate], // Document.startDate is optional
+ providerType: String,
+ patientId: UuidId[Patient],
+ labelId: LongId[Label],
+ isImplicitMatch: Boolean)
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala
index e3b323f..82204c2 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala
@@ -12,7 +12,6 @@ case class RawTrialLabel(nctId: StringId[Trial],
armName: String,
armId: LongId[Arm],
labelId: LongId[Label],
- label: String,
value: Option[Boolean],
criterionId: LongId[Criterion],
criteria: String,
@@ -25,7 +24,7 @@ object RawTrialLabel {
implicit def toPhiString(x: RawTrialLabel): PhiString = {
import x._
phi"RawTrialLabel(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, lastReviewed=$lastReviewed, " +
- phi"armId=$armId, armName=${Unsafe(armName)}, labelId=$labelId, label=${Unsafe(label)}, value=$value, " +
+ phi"armId=$armId, armName=${Unsafe(armName)}, labelId=$labelId, value=$value, " +
phi"criterionId=$criterionId, criteria=${Unsafe(criteria)}, criterionArmId=$criterionArmId, " +
phi"isCompound=$isCompound, isDefining=$isDefining)"
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala
index f5ab3cb..0f478ca 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala
@@ -57,8 +57,8 @@ object Trial {
implicit def toPhiString(x: Trial): PhiString = {
import x._
phi"Trial(id=$id, externalId=$externalId, status=$status, previousStatus=$previousStatus, " +
- phi"assignee=$assignee, previousAssignee=$previousAssignee, isSummaryReviewed=$isSummaryReviewed, " +
- phi"isCriteriaReviewed=$isCriteriaReviewed)"
+ phi"lastActiveUserId=$lastActiveUserId, assignee=$assignee, previousAssignee=$previousAssignee, " +
+ phi"isSummaryReviewed=$isSummaryReviewed, isCriteriaReviewed=$isCriteriaReviewed)"
}
case class Locations(locations: List[String])
@@ -81,6 +81,7 @@ final case class Trial(id: StringId[Trial],
assignee: Option[LongId[User]],
previousStatus: Option[Status],
previousAssignee: Option[LongId[User]],
+ lastActiveUserId: Option[LongId[User]],
lastUpdate: LocalDateTime,
condition: Condition,
phase: String,
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/UserHistory.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/UserHistory.scala
new file mode 100644
index 0000000..b55369e
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/UserHistory.scala
@@ -0,0 +1,156 @@
+package xyz.driver.pdsuidomain.entities
+
+import java.time.{LocalDateTime, ZoneId}
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuicommon.utils.Utils
+
+final case class UserHistory(id: LongId[UserHistory],
+ executor: LongId[User],
+ recordId: Option[LongId[MedicalRecord]] = None,
+ documentId: Option[LongId[Document]] = None,
+ trialId: Option[StringId[Trial]] = None,
+ patientId: Option[UuidId[Patient]] = None,
+ state: UserHistory.State,
+ action: UserHistory.Action,
+ created: LocalDateTime = LocalDateTime.now(ZoneId.of("Z")))
+
+object UserHistory {
+
+ def forDocument(executor: LongId[User],
+ documentId: LongId[Document],
+ state: UserHistory.State,
+ action: UserHistory.Action): UserHistory = UserHistory (
+ id = LongId(0L),
+ executor = executor,
+ documentId = Some(documentId),
+ state = state,
+ action = action
+ )
+
+ def forRecord(executor: LongId[User],
+ recordId: LongId[MedicalRecord],
+ state: UserHistory.State,
+ action: UserHistory.Action): UserHistory = UserHistory (
+ id = LongId(0L),
+ executor = executor,
+ recordId = Some(recordId),
+ state = state,
+ action = action
+ )
+
+ def forTrial(executor: LongId[User],
+ trialId: StringId[Trial],
+ state: UserHistory.State,
+ action: UserHistory.Action): UserHistory = UserHistory (
+ id = LongId(0L),
+ executor = executor,
+ trialId = Some(trialId),
+ state = state,
+ action = action
+ )
+
+ def forPatient(executor: LongId[User],
+ patientId: UuidId[Patient],
+ state: UserHistory.State,
+ action: UserHistory.Action): UserHistory = UserHistory (
+ id = LongId(0L),
+ executor = executor,
+ patientId = Some(patientId),
+ state = state,
+ action = action
+ )
+
+ sealed trait State extends Product with Serializable {
+
+ def oneOf(xs: State*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[State]): Boolean = xs.contains(this)
+ }
+
+ object State {
+ case object Clean extends State
+ case object Organize extends State
+ case object Extract extends State
+ case object Summarize extends State
+ case object Criteriarize extends State
+ case object Verify extends State
+ case object Curate extends State
+ case object Review extends State
+ case object Flag extends State
+
+ val All: Set[State] = Set[State](Clean, Organize, Extract, Summarize, Criteriarize, Verify, Curate, Review, Flag)
+
+ val fromString: PartialFunction[String, State] = {
+ case "Clean" => State.Clean
+ case "Organize" => State.Organize
+ case "Extract" => State.Extract
+ case "Summarize" => State.Summarize
+ case "Criteriarize" => State.Criteriarize
+ case "Verify" => State.Verify
+ case "Curate" => State.Curate
+ case "Review" => State.Review
+ case "Flag" => State.Flag
+ }
+
+ def stateToString(x: State): String = x match {
+ case State.Clean => "Clean"
+ case State.Organize => "Organize"
+ case State.Extract => "Extract"
+ case State.Summarize => "Summarize"
+ case State.Criteriarize => "Criteriarize"
+ case State.Verify => "Verify"
+ case State.Curate => "Curate"
+ case State.Review => "Review"
+ case State.Flag => "Flag"
+ }
+
+ implicit def toPhiString(x: State): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ sealed trait Action extends Product with Serializable {
+
+ def oneOf(xs: Action*): Boolean = xs.contains(this)
+
+ def oneOf(xs: Set[Action]): Boolean = xs.contains(this)
+ }
+
+ object Action {
+ case object Start extends Action
+ case object Submit extends Action
+ case object Unassign extends Action
+ case object Resolve extends Action
+ case object Flag extends Action
+ case object Archive extends Action
+
+ val All: Set[Action] = Set[Action](Start, Submit, Unassign, Resolve, Flag, Archive)
+
+ val fromString: PartialFunction[String, Action] = {
+ case "Start" => Action.Start
+ case "Submit" => Action.Submit
+ case "Unassign" => Action.Unassign
+ case "Resolve" => Action.Resolve
+ case "Flag" => Action.Flag
+ case "Archive" => Action.Archive
+ }
+
+ def actionToString(x: Action): String = x match {
+ case Action.Start => "Start"
+ case Action.Submit => "Submit"
+ case Action.Unassign => "Unassign"
+ case Action.Resolve => "Resolve"
+ case Action.Flag => "Flag"
+ case Action.Archive => "Archive"
+ }
+
+ implicit def toPhiString(x: Action): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
+ }
+
+ implicit def toPhiString(x: UserHistory): PhiString = {
+ import x._
+ phi"UserHistory(id=$id, executor=$executor, recordId=$recordId, " +
+ phi"documentId=$documentId, trialId=$trialId, patientId=$patientId, " +
+ phi"state=$state, action=$action, created=$created)"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala
new file mode 100644
index 0000000..5474413
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabel.scala
@@ -0,0 +1,30 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain.LongId
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Label, RawPatientLabel}
+
+final case class ExportPatientLabel(id: LongId[Label],
+ name: String,
+ evidences: List[ExportPatientLabelEvidence])
+
+object ExportPatientLabel extends PhiLogging {
+
+ implicit def toPhiString(x: ExportPatientLabel): PhiString = {
+ import x._
+ phi"ExportPatientLabel(id=$id, evidences=$evidences)"
+ }
+
+ def fromRaw(labelId: LongId[Label], rawPatientLabels: List[RawPatientLabel]): ExportPatientLabel = {
+ val firstLabel = rawPatientLabels.headOption
+ if (firstLabel.isEmpty) {
+ logger.warn(phi"rawPatientLabels is empty, labelId: $labelId")
+ }
+
+ ExportPatientLabel(
+ id = labelId,
+ name = firstLabel.map(_.label).getOrElse(""),
+ evidences = rawPatientLabels.map(ExportPatientLabelEvidence.fromRaw)
+ )
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala
new file mode 100644
index 0000000..fb40339
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidence.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{ExtractedData, RawPatientLabel}
+
+final case class ExportPatientLabelEvidence(id: LongId[ExtractedData],
+ value: FuzzyValue,
+ evidenceText: String,
+ document: ExportPatientLabelEvidenceDocument)
+
+object ExportPatientLabelEvidence {
+
+ implicit def toPhiString(x: ExportPatientLabelEvidence): PhiString = {
+ import x._
+ phi"ExportPatientLabelEvidence(id=${Unsafe(id)}, value=$value, " +
+ phi"evidenceText=${Unsafe(evidenceText)}, document=$document)"
+ }
+
+ def fromRaw(x: RawPatientLabel) = ExportPatientLabelEvidence(
+ id = x.evidenceId,
+ value = x.value,
+ evidenceText = x.evidenceText,
+ document = ExportPatientLabelEvidenceDocument(
+ x.documentId,
+ x.requestId,
+ x.documentType,
+ x.providerType,
+ x.startDate
+ )
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala
new file mode 100644
index 0000000..99912bc
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientLabelEvidenceDocument.scala
@@ -0,0 +1,30 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import java.time.LocalDate
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Document, RawPatientLabel, RecordRequestId}
+
+final case class ExportPatientLabelEvidenceDocument(documentId: LongId[Document],
+ requestId: RecordRequestId,
+ documentType: String,
+ providerType: String,
+ date: LocalDate)
+
+object ExportPatientLabelEvidenceDocument extends PhiLogging {
+
+ implicit def toPhiString(x: ExportPatientLabelEvidenceDocument): PhiString = {
+ import x._
+ phi"ExportPatientLabelEvidenceDocument(documentId=$documentId, requestId=$requestId, " +
+ phi"documentType=${Unsafe(documentType)}, providerType=${Unsafe(providerType)}, date=$date)"
+ }
+
+ def fromRaw(x: RawPatientLabel) = ExportPatientLabelEvidenceDocument(
+ documentId = x.documentId,
+ requestId = x.requestId,
+ documentType = x.documentType,
+ providerType = x.providerType,
+ date = x.startDate
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala
new file mode 100644
index 0000000..e6262ed
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/patient/ExportPatientWithLabels.scala
@@ -0,0 +1,26 @@
+package xyz.driver.pdsuidomain.entities.export.patient
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Patient, RawPatientLabel}
+
+import scala.collection.breakOut
+
+final case class ExportPatientWithLabels(patientId: UuidId[Patient], labelVersion: Long, labels: List[ExportPatientLabel])
+
+object ExportPatientWithLabels {
+
+ implicit def toPhiString(x: ExportPatientWithLabels): PhiString = {
+ import x._
+ phi"ExportPatientWithLabels(patientId=$patientId, version=${Unsafe(labelVersion)}, labels=$labels)"
+ }
+
+ def fromRaw(patientId: UuidId[Patient],
+ rawPatientRefs: List[RawPatientLabel]) = ExportPatientWithLabels(
+ patientId = patientId,
+ labelVersion = 1L, // TODO It is needed to replace this mock label version.
+ labels = rawPatientRefs
+ .groupBy(_.labelId)
+ .map(Function.tupled(ExportPatientLabel.fromRaw))(breakOut)
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala
new file mode 100644
index 0000000..1aed121
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrial.scala
@@ -0,0 +1,27 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Trial
+
+final case class ExportTrial(nctId: StringId[Trial],
+ trialId: UuidId[Trial],
+ condition: Trial.Condition,
+ lastReviewed: LocalDateTime)
+
+object ExportTrial {
+
+ implicit def toPhiString(x: ExportTrial): PhiString = {
+ import x._
+ phi"ExportTrial(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, lastReviewed=$lastReviewed)"
+ }
+
+ def fromDomain(x: Trial) = ExportTrial(
+ nctId = x.id,
+ trialId = x.externalId,
+ condition = x.condition,
+ lastReviewed = x.lastUpdate
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala
new file mode 100644
index 0000000..5a9a406
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialArm.scala
@@ -0,0 +1,15 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.Arm
+
+final case class ExportTrialArm(armId: LongId[Arm], armName: String)
+
+object ExportTrialArm {
+
+ implicit def toPhiString(x: ExportTrialArm): PhiString = {
+ import x._
+ phi"ExportTrialArm(armId=$armId, armName=${Unsafe(armName)})"
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala
new file mode 100644
index 0000000..7bff22c
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala
@@ -0,0 +1,32 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{Arm, Criterion, Label, RawTrialLabel}
+
+final case class ExportTrialLabelCriterion(criterionId: LongId[Criterion],
+ value: Option[Boolean],
+ labelId: LongId[Label],
+ armIds: Set[LongId[Arm]],
+ criteria: String,
+ isCompound: Boolean,
+ isDefining: Boolean)
+
+object ExportTrialLabelCriterion {
+
+ implicit def toPhiString(x: ExportTrialLabelCriterion): PhiString = {
+ import x._
+ phi"TrialLabelCriterion(criterionId=$criterionId, value=$value, labelId=$labelId, " +
+ phi"criteria=${Unsafe(criteria)}, isCompound=$isCompound, isDefining=$isDefining)"
+ }
+
+ def fromRaw(x: RawTrialLabel, armIds: Set[LongId[Arm]]) = ExportTrialLabelCriterion(
+ criterionId = x.criterionId,
+ value = x.value,
+ labelId = x.labelId,
+ armIds = armIds,
+ criteria = x.criteria,
+ isCompound = x.isCompound,
+ isDefining = x.isDefining
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala
new file mode 100644
index 0000000..1655a88
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala
@@ -0,0 +1,52 @@
+package xyz.driver.pdsuidomain.entities.export.trial
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{RawTrialLabel, Trial}
+
+import scala.collection.breakOut
+
+final case class ExportTrialWithLabels(nctId: StringId[Trial],
+ trialId: UuidId[Trial],
+ condition: String,
+ lastReviewed: LocalDateTime,
+ labelVersion: Long,
+ arms: List[ExportTrialArm],
+ criteria: List[ExportTrialLabelCriterion])
+
+object ExportTrialWithLabels {
+
+ implicit def toPhiString(x: ExportTrialWithLabels): PhiString = {
+ import x._
+ phi"TrialWithLabels(nctId=$nctId, trialId=$trialId, condition=${Unsafe(condition)}, " +
+ phi"lastReviewed=$lastReviewed, labelVersion=${Unsafe(labelVersion)}, arms=$arms, criteria=$criteria)"
+ }
+
+ def fromRaw(rawData: List[RawTrialLabel]): ExportTrialWithLabels = {
+ val trials: Set[StringId[Trial]] = rawData.map(_.nctId)(breakOut)
+
+ assert(trials.size == 1, "There are more than one trials in the rawData")
+ val trial = rawData.head
+
+ ExportTrialWithLabels(
+ nctId = trial.nctId,
+ trialId = trial.trialId,
+ condition = trial.condition,
+ lastReviewed = trial.lastReviewed,
+ labelVersion = 1, // TODO It is needed to replace this mock label version.
+ arms = rawData.groupBy(_.armId).map { case (armId, rawTrials) =>
+ ExportTrialArm(armId, rawTrials.head.armName)
+ }(breakOut),
+ criteria = rawData.groupBy { x =>
+ (x.criterionId, x.labelId)
+ }.map {
+ case (_, rawTrialLabels) =>
+ val armIds = rawTrialLabels.map(_.criterionArmId).toSet
+ ExportTrialLabelCriterion.fromRaw(rawTrialLabels.head, armIds)
+ }(breakOut)
+ )
+ }
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala
index 604a98b..ab7641f 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala
@@ -11,6 +11,7 @@ import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion
final case class ApiNewCriterion(meta: Option[String],
arms: Option[Seq[Long]],
text: Option[String],
+ isCompound: Option[Boolean],
labels: Seq[ApiCriterionLabel],
trialId: String) {
@@ -19,7 +20,7 @@ final case class ApiNewCriterion(meta: Option[String],
id = LongId(0L),
meta = meta.getOrElse(""),
trialId = StringId(trialId),
- isCompound = false,
+ isCompound = isCompound.getOrElse(false),
text = text
),
armIds = arms.getOrElse(Seq.empty).map(LongId[Arm]),
@@ -35,6 +36,7 @@ object ApiNewCriterion {
}, Writes[String](Json.parse))) and
(JsPath \ "arms").formatNullable(seqJsonFormat[Long]) and
(JsPath \ "text").formatNullable[String] and
+ (JsPath \ "isCompound").formatNullable[Boolean] 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/document/ApiDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala
index 0fd96b3..73b6166 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala
@@ -17,18 +17,20 @@ final case class ApiDocument(id: Long,
endDate: Option[LocalDate],
provider: Option[String],
providerTypeId: Option[Long],
+ requiredType: Option[String],
status: Option[String],
previousStatus: Option[String],
assignee: Option[Long],
previousAssignee: Option[Long],
+ lastActiveUser: 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
+ case x if Document.RequiredType.fromString.isDefinedAt(x) => true
+ case _ => false
},
Writes.StringWrites
)
@@ -43,10 +45,12 @@ object ApiDocument {
(JsPath \ "endDate").formatNullable[LocalDate] and
(JsPath \ "provider").formatNullable[String] and
(JsPath \ "providerTypeId").formatNullable[Long] and
+ (JsPath \ "requiredType").formatNullable[String] and
(JsPath \ "status").formatNullable(statusFormat) and
(JsPath \ "previousStatus").formatNullable(statusFormat) and
(JsPath \ "assignee").formatNullable[Long] and
(JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "lastActiveUser").formatNullable[Long] and
(JsPath \ "meta").formatNullable(Format(Reads { x =>
JsSuccess(Json.stringify(x))
}, Writes[String](Json.parse)))
@@ -63,10 +67,12 @@ object ApiDocument {
endDate = document.endDate,
provider = document.providerName,
providerTypeId = document.providerTypeId.map(_.id),
- status = Option(DocumentUtils.statusToString(document.status)),
- previousStatus = document.previousStatus.map(DocumentUtils.statusToString),
+ requiredType = document.requiredType.map(Document.RequiredType.requiredTypeToString),
+ status = Option(Document.Status.statusToString(document.status)),
+ previousStatus = document.previousStatus.map(Document.Status.statusToString),
assignee = document.assignee.map(_.id),
previousAssignee = document.previousAssignee.map(_.id),
+ lastActiveUser = document.lastActiveUserId.map(_.id),
meta = document.meta.map(meta => JsonSerializer.serialize(meta.content))
)
}
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
index 7682bb5..84e9e09 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala
@@ -31,15 +31,17 @@ final case class ApiPartialDocument(recordId: Option[Long],
def applyTo(orig: Document): Document = Document(
id = orig.id,
- status = status.map(DocumentUtils.statusFromString).getOrElse(orig.status),
+ status = status.map(Document.Status.fromString).getOrElse(orig.status),
previousStatus = orig.previousStatus,
assignee = assignee.map(LongId[User]).cata(Some(_), None, orig.assignee),
previousAssignee = orig.previousAssignee,
+ lastActiveUserId = orig.lastActiveUserId,
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),
+ requiredType = orig.requiredType,
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),
@@ -64,10 +66,12 @@ final case class ApiPartialDocument(recordId: Option[Long],
endDate = endDate.toOption,
providerName = provider.toOption,
providerTypeId = providerTypeId.map(LongId[ProviderType]).toOption,
+ requiredType = None,
meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).toOption,
previousStatus = None,
assignee = None,
previousAssignee = None,
+ lastActiveUserId = None,
lastUpdate = LocalDateTime.MIN
)
} else {
@@ -87,7 +91,7 @@ object ApiPartialDocument {
(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 x if Document.Status.fromString.isDefinedAt(x) => true
case _ => false
})) and
(JsPath \ "assignee").readTristate[Long] and
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
deleted file mode 100644
index 24e388e..0000000
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-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
index 2bb4945..e0f23e3 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala
@@ -2,9 +2,9 @@ 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
+import xyz.driver.pdsuidomain.entities.PatientLabelEvidenceView
final case class ApiPatientLabelEvidence(id: Long,
value: String,
@@ -20,19 +20,15 @@ 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
- )
- }
+ def fromDomain(x: PatientLabelEvidenceView) = ApiPatientLabelEvidence(
+ id = x.id.id,
+ value = FuzzyValue.valueToString(x.value),
+ evidenceText = x.evidenceText,
+ documentId = x.documentId.map(_.id),
+ evidenceId = x.evidenceId.map(_.id),
+ reportId = x.reportId.map(_.toString),
+ documentType = x.documentType,
+ date = x.date.get,
+ providerType = x.providerType
+ )
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala
new file mode 100644
index 0000000..20d4a74
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala
@@ -0,0 +1,33 @@
+
+package xyz.driver.pdsuidomain.formats.json.linkedpatient
+
+import java.util.UUID
+
+import play.api.libs.json.{Format, Json}
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuidomain.services.LinkedPatientService.RichLinkedPatient
+
+case class ApiLinkedPatient(email: String,
+ name: String,
+ patientId: UUID,
+ trialId: String) {
+
+ def toDomain = RichLinkedPatient(
+ email = Email(email),
+ name = name,
+ patientId = UuidId(patientId),
+ trialId = StringId(trialId)
+ )
+}
+
+object ApiLinkedPatient {
+
+ implicit val format: Format[ApiLinkedPatient] = Json.format[ApiLinkedPatient]
+
+ def fromDomain(entity: RichLinkedPatient) = ApiLinkedPatient(
+ email = entity.email.value,
+ name = entity.name,
+ patientId = entity.patientId.id,
+ trialId = entity.trialId.id
+ )
+}
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
index 8c390e7..88dd5a3 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala
@@ -50,7 +50,6 @@ final case class ApiPartialMessage(text: Option[String],
lastUpdate = LocalDateTime.MIN
)
}
-
}
object ApiPartialMessage {
@@ -80,5 +79,4 @@ object ApiPartialMessage {
archiveRequired = domain.archiveRequired,
meta = domain.meta
)
-
}
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
index caa556b..68e965c 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala
@@ -13,6 +13,7 @@ final case class ApiPatient(id: String,
assignee: Option[Long],
previousStatus: Option[String],
previousAssignee: Option[Long],
+ lastActiveUser: Option[Long],
lastUpdate: ZonedDateTime,
condition: String)
@@ -26,6 +27,7 @@ object ApiPatient {
(JsPath \ "assignee").formatNullable[Long] and
(JsPath \ "previousStatus").formatNullable[String] and
(JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "lastActiveUser").formatNullable[Long] and
(JsPath \ "lastUpdate").format[ZonedDateTime] and
(JsPath \ "condition").format[String]
)(ApiPatient.apply, unlift(ApiPatient.unapply))
@@ -38,6 +40,7 @@ object ApiPatient {
assignee = patient.assignee.map(_.id),
previousStatus = patient.previousStatus.map(PatientStatus.statusToString),
previousAssignee = patient.previousAssignee.map(_.id),
+ lastActiveUser = patient.lastActiveUserId.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/eligible/ApiPatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala
index 42c3259..033d73e 100644
--- 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
@@ -14,7 +14,7 @@ final case class ApiPatientEligibleTrial(id: Long,
trialTitle: String,
arms: List[String],
hypothesisId: UUID,
- eligibilityStatus: Option[String],
+ verifiedEligibilityStatus: Option[String],
isVerified: Boolean)
object ApiPatientEligibleTrial {
@@ -26,7 +26,7 @@ object ApiPatientEligibleTrial {
(JsPath \ "trialTitle").format[String] and
(JsPath \ "arms").format[List[String]] and
(JsPath \ "hypothesisId").format[UUID] and
- (JsPath \ "eligibilityStatus").formatNullable[String](Format(
+ (JsPath \ "verifiedEligibilityStatus").formatNullable[String](Format(
Reads
.of[String]
.filter(ValidationError("unknown eligibility status"))({
@@ -45,7 +45,7 @@ object ApiPatientEligibleTrial {
trialTitle = eligibleTrialWithTrial.trial.title,
arms = eligibleTrialWithTrial.arms.map(_.name),
hypothesisId = eligibleTrialWithTrial.group.hypothesisId.id,
- eligibleTrialWithTrial.group.eligibilityStatus.map(FuzzyValue.valueToString),
+ eligibleTrialWithTrial.group.verifiedEligibilityStatus.map(FuzzyValue.valueToString),
eligibleTrialWithTrial.group.isVerified
)
}
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
index 374370e..584ff72 100644
--- 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
@@ -10,7 +10,8 @@ final case class ApiPatientHypothesis(id: UUID,
patientId: String,
hypothesisId: UUID,
matchedTrials: Long,
- rationale: Option[String])
+ rationale: Option[String],
+ isRationaleRequired: Boolean)
object ApiPatientHypothesis {
@@ -19,14 +20,16 @@ object ApiPatientHypothesis {
(JsPath \ "patientId").format[String] and
(JsPath \ "hypothesisId").format[UUID] and
(JsPath \ "matchedTrials").format[Long] and
- (JsPath \ "rationale").formatNullable[String]
+ (JsPath \ "rationale").formatNullable[String] and
+ (JsPath \ "isRationaleRequired").format[Boolean]
)(ApiPatientHypothesis.apply, unlift(ApiPatientHypothesis.unapply))
- def fromDomain(patientHypothesis: PatientHypothesis) = ApiPatientHypothesis(
+ def fromDomain(patientHypothesis: PatientHypothesis, isRationaleRequired: Boolean) = ApiPatientHypothesis(
id = patientHypothesis.id.id,
patientId = patientHypothesis.patientId.toString,
hypothesisId = patientHypothesis.hypothesisId.id,
matchedTrials = patientHypothesis.matchedTrials,
- rationale = patientHypothesis.rationale
+ rationale = patientHypothesis.rationale,
+ isRationaleRequired = isRationaleRequired
)
}
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
index 40f7de4..91d0a0e 100644
--- 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
@@ -17,7 +17,6 @@ final case class ApiPartialPatientLabel(primaryValue: Option[String], verifiedPr
verifiedPrimaryValue.cata(x => Some(FuzzyValue.fromString(x)), None, orig.verifiedPrimaryValue)
)
}
-
}
object ApiPartialPatientLabel {
@@ -42,5 +41,4 @@ object ApiPartialPatientLabel {
Writes.of[String]
))
)(ApiPartialPatientLabel.apply, unlift(ApiPartialPatientLabel.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
index 3e2de99..7ac55f6 100644
--- 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
@@ -11,6 +11,7 @@ import play.api.libs.json.{Format, JsPath, Reads, Writes}
final case class ApiPatientCriterion(id: Long,
labelId: Long,
nctId: String,
+ criterionId: Long,
criterionText: String,
criterionValue: Option[String],
criterionIsDefining: Boolean,
@@ -28,6 +29,7 @@ object ApiPatientCriterion {
(JsPath \ "id").format[Long] and
(JsPath \ "labelId").format[Long] and
(JsPath \ "nctId").format[String] and
+ (JsPath \ "criterionId").format[Long] and
(JsPath \ "criterionText").format[String] and
(JsPath \ "criterionValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ x =>
x == "Yes" || x == "No"
@@ -56,6 +58,7 @@ object ApiPatientCriterion {
id = patientCriterion.id.id,
labelId = labelId.id,
nctId = patientCriterion.nctId.id,
+ criterionId = patientCriterion.criterionId.id,
criterionText = patientCriterion.criterionText,
criterionValue = patientCriterion.criterionValue.map { x =>
FuzzyValue.valueToString(FuzzyValue.fromBoolean(x))
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
index cee67b2..e96bc81 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala
@@ -15,6 +15,7 @@ final case class ApiCreateRecord(disease: String, patientId: String, requestId:
previousStatus = None,
assignee = None,
previousAssignee = None,
+ lastActiveUserId = None,
patientId = UuidId(patientId),
requestId = RecordRequestId(requestId),
disease = disease,
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
index de54a9e..dca441b 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala
@@ -17,6 +17,7 @@ final case class ApiRecord(id: Long,
previousStatus: Option[String],
assignee: Option[Long],
previousAssignee: Option[Long],
+ lastActiveUser: Option[Long],
meta: String)
object ApiRecord {
@@ -39,6 +40,7 @@ object ApiRecord {
(JsPath \ "previousStatus").formatNullable(statusFormat) and
(JsPath \ "assignee").formatNullable[Long] and
(JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "lastActiveUser").formatNullable[Long] and
(JsPath \ "meta").format(Format(Reads { x =>
JsSuccess(Json.stringify(x))
}, Writes[String](Json.parse)))
@@ -54,6 +56,7 @@ object ApiRecord {
previousStatus = record.previousStatus.map(MedicalRecordStatus.statusToString),
assignee = record.assignee.map(_.id),
previousAssignee = record.previousAssignee.map(_.id),
+ lastActiveUser = record.lastActiveUserId.map(_.id),
meta = record.meta.map(x => JsonSerializer.serialize(x.content)).getOrElse("[]")
)
}
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
index db980ee..97bab5e 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala
@@ -13,6 +13,7 @@ final case class ApiTrial(id: String,
assignee: Option[Long],
previousStatus: Option[String],
previousAssignee: Option[Long],
+ lastActiveUser: Option[Long],
condition: Option[String],
phase: Option[String],
hypothesisId: Option[UUID],
@@ -32,6 +33,7 @@ object ApiTrial {
(JsPath \ "assignee").formatNullable[Long] and
(JsPath \ "previousStatus").formatNullable[String] and
(JsPath \ "previousAssignee").formatNullable[Long] and
+ (JsPath \ "lastActiveUser").formatNullable[Long] and
(JsPath \ "condition").formatNullable[String] and
(JsPath \ "phase").formatNullable[String] and
(JsPath \ "hypothesisId").formatNullable[UUID] and
@@ -49,6 +51,7 @@ object ApiTrial {
assignee = trial.assignee.map(_.id),
previousStatus = trial.previousStatus.map(TrialStatus.statusToString),
previousAssignee = trial.previousAssignee.map(_.id),
+ lastActiveUser = trial.lastActiveUserId.map(_.id),
lastUpdate = Option(ZonedDateTime.of(trial.lastUpdate, ZoneId.of("Z"))),
condition = Option(trial.condition.toString),
phase = Option(trial.phase),
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
index 654508c..a37fea8 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala
@@ -1,8 +1,5 @@
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._
@@ -10,7 +7,7 @@ import play.api.libs.json._
import scala.collection._
import scala.util.Try
-import ApiPartialUser._
+import User._
import xyz.driver.pdsuicommon.json.JsonValidationException
import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors}
@@ -62,19 +59,12 @@ final case class ApiPartialUser(email: Option[String], name: Option[String], rol
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 \ "name").formatNullable[String](Format(
+ Reads.filterNot[String](ValidationError("Username is too long (max length is 255 chars)", 255))(_.size > 255),
+ Writes.StringWrites
+ )) and
(JsPath \ "roleId").formatNullable[String](
Format(Reads
.of[String]
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/userhistory/ApiUserHistory.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/userhistory/ApiUserHistory.scala
new file mode 100644
index 0000000..befff05
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/userhistory/ApiUserHistory.scala
@@ -0,0 +1,43 @@
+package xyz.driver.pdsuidomain.formats.json.userhistory
+
+import java.time.{ZoneId, ZonedDateTime}
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, JsPath}
+import xyz.driver.pdsuidomain.entities.UserHistory
+
+final case class ApiUserHistory(id: Long,
+ executor: Long,
+ recordId: Option[Long],
+ documentId: Option[Long],
+ trialId: Option[String],
+ patientId: Option[String],
+ state: String,
+ action: String,
+ created: ZonedDateTime)
+
+object ApiUserHistory {
+ implicit val format: Format[ApiUserHistory] = (
+ (JsPath \ "id").format[Long] and
+ (JsPath \ "executor").format[Long] and
+ (JsPath \ "recordId").formatNullable[Long] and
+ (JsPath \ "documentId").formatNullable[Long] and
+ (JsPath \ "trialId").formatNullable[String] and
+ (JsPath \ "patientId").formatNullable[String] and
+ (JsPath \ "state").format[String] and
+ (JsPath \ "action").format[String] and
+ (JsPath \ "created").format[ZonedDateTime]
+ ) (ApiUserHistory.apply, unlift(ApiUserHistory.unapply))
+
+ def fromDomain(x: UserHistory) = ApiUserHistory(
+ id = x.id.id,
+ executor = x.executor.id,
+ recordId = x.recordId.map(_.id),
+ documentId = x.documentId.map(_.id),
+ trialId = x.trialId.map(_.id),
+ patientId = x.patientId.map(_.id.toString),
+ state = UserHistory.State.stateToString(x.state),
+ action = UserHistory.Action.actionToString(x.action),
+ created = ZonedDateTime.of(x.created, ZoneId.of("Z"))
+ )
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala
index 43e46ed..8c89505 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala
@@ -33,7 +33,6 @@ object ArmService {
case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext)
extends GetByIdReply with DomainError
-
}
sealed trait GetListReply
@@ -44,13 +43,12 @@ object ArmService {
case object AuthorizationError
extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
-
}
sealed trait UpdateReply
object UpdateReply {
- case class Updated(updated: Arm) extends UpdateReply
+ final case class Updated(updated: Arm) extends UpdateReply
type Error = UpdateReply with DomainError
@@ -59,9 +57,9 @@ object ArmService {
case object AuthorizationError
extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
- case class CommonError(userMessage: String) extends UpdateReply with DomainError
+ final case class CommonError(userMessage: String) extends UpdateReply with DomainError
- case class AlreadyExistsError(x: Arm) extends UpdateReply with DomainError {
+ final case class AlreadyExistsError(x: Arm) extends UpdateReply with DomainError {
val userMessage = s"The arm with such name of trial already exists."
}
@@ -80,9 +78,9 @@ object ArmService {
case object AuthorizationError
extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError
- case class CommonError(userMessage: String) extends CreateReply with DomainError
+ final case class CommonError(userMessage: String) extends CreateReply with DomainError
- case class AlreadyExistsError(x: Arm) extends CreateReply with DomainError {
+ final case class AlreadyExistsError(x: Arm) extends CreateReply with DomainError {
val userMessage = s"The arm with this name of trial already exists."
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala
index a735ade..f8d820b 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala
@@ -3,7 +3,6 @@ package xyz.driver.pdsuidomain.services
import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
import xyz.driver.pdsuicommon.db.Sorting
import xyz.driver.pdsuicommon.error.DomainError
-import xyz.driver.pdsuicommon.logging._
import xyz.driver.pdsuidomain.entities.CategoryWithLabels
import scala.concurrent.Future
@@ -19,7 +18,7 @@ object CategoryService {
}
}
-trait CategoryService extends PhiLogging {
+trait CategoryService {
import CategoryService._
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala
index ffa252c..2c01a7e 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala
@@ -99,7 +99,6 @@ object DocumentService {
case class CommonError(userMessage: String) extends DeleteReply with DomainError
}
-
}
trait DocumentService {
@@ -115,7 +114,6 @@ trait DocumentService {
def create(draftDocument: Document)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
- // Update operations are validated in internal.*Command
def update(orig: Document, draft: Document)(
implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply]
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala
new file mode 100644
index 0000000..8fb399a
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala
@@ -0,0 +1,64 @@
+package xyz.driver.pdsuidomain.services
+
+import java.time.LocalDateTime
+
+import xyz.driver.pdsuicommon.auth.AnonymousRequestContext
+import xyz.driver.pdsuicommon.db.SearchFilterExpr
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuidomain.entities.export.patient.ExportPatientWithLabels
+import xyz.driver.pdsuidomain.entities.export.trial.{ExportTrial, ExportTrialWithLabels}
+import xyz.driver.pdsuidomain.entities.{MedicalRecord, Patient, Trial}
+
+import scala.concurrent.Future
+
+object ExportService {
+
+ sealed trait GetPatientReply
+ object GetPatientReply {
+ type Error = GetPatientReply with DomainError
+
+ case class Entity(x: ExportPatientWithLabels) extends GetPatientReply
+
+ case object NotFoundError extends GetPatientReply with DomainError.NotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+ }
+
+ sealed trait GetTrialListReply
+ object GetTrialListReply {
+ case class EntityList(xs: Seq[ExportTrial], totalFound: Int, lastUpdate: Option[LocalDateTime])
+ extends GetTrialListReply
+ }
+
+ sealed trait GetTrialReply
+ object GetTrialReply {
+ type Error = GetTrialReply with DomainError
+
+ case class Entity(x: ExportTrialWithLabels) extends GetTrialReply
+
+ case object NotFoundError extends GetTrialReply with DomainError.NotFoundError {
+ def userMessage: String = "Trial not found"
+ }
+ }
+}
+
+trait ExportService {
+
+ import ExportService._
+
+ def getPatient(id: UuidId[Patient])
+ (implicit requestContext: AnonymousRequestContext): Future[GetPatientReply]
+
+ def getTrialList(filter: SearchFilterExpr = SearchFilterExpr.Empty)
+ (implicit requestContext: AnonymousRequestContext): Future[GetTrialListReply]
+
+ def getTrial(trialId: StringId[Trial], condition: String)
+ (implicit requestContext: AnonymousRequestContext): Future[GetTrialReply]
+
+ def getRecords(patientId: UuidId[Patient])
+ (implicit requestContext: AnonymousRequestContext): Future[MedicalRecordService.GetListReply]
+
+ def getRecordPdf(recordId: LongId[MedicalRecord])
+ (implicit requestContext: AnonymousRequestContext): Future[MedicalRecordService.GetPdfSourceReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala
index 0677584..aadc5fb 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala
@@ -3,7 +3,6 @@ package xyz.driver.pdsuidomain.services
import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
import xyz.driver.pdsuicommon.db.Sorting
import xyz.driver.pdsuicommon.error.DomainError
-import xyz.driver.pdsuicommon.logging.PhiLogging
import xyz.driver.pdsuidomain.entities.Label
import scala.concurrent.Future
@@ -20,7 +19,7 @@ object LabelService {
}
}
-trait LabelService extends PhiLogging {
+trait LabelService {
import LabelService._
def getAll(sorting: Option[Sorting] = None)(
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala
new file mode 100644
index 0000000..8fc1662
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala
@@ -0,0 +1,62 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.domain._
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.entities.{LinkedPatient, Patient, Trial}
+
+import scala.concurrent.Future
+
+object LinkedPatientService {
+
+ trait DefaultTrialNotFoundError {
+ def userMessage: String = "Trial not found"
+ }
+
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
+ final case class RichLinkedPatient(email: Email,
+ name: String,
+ patientId: UuidId[Patient],
+ trialId: StringId[Trial]) {
+ def toLinkedPatient(user: User) = LinkedPatient(
+ userId = user.id,
+ patientId = patientId,
+ trialId = trialId
+ )
+ }
+
+ object RichLinkedPatient {
+ implicit def toPhiString(x: RichLinkedPatient): PhiString = {
+ import x._
+ phi"RichLinkedPatient(email=${Unsafe(email)}, patientId=$patientId, trialId=$trialId)"
+ }
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+
+ /**
+ * @param createdUser None if a user was created before
+ */
+ final case class Created(x: RichLinkedPatient, createdUser: Option[User]) extends CreateReply
+
+ case object PatientNotFoundError
+ extends CreateReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ case object TrialNotFoundError
+ extends CreateReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
+ final case class CommonError(userMessage: String) extends CreateReply with DomainError
+ }
+}
+
+trait LinkedPatientService {
+
+ import LinkedPatientService._
+
+ def create(entity: RichLinkedPatient): Future[CreateReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala
new file mode 100644
index 0000000..3022716
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala
@@ -0,0 +1,39 @@
+package xyz.driver.pdsuidomain.services
+
+import java.io.{InputStream, StringReader, StringWriter}
+
+import xyz.driver.pdsuidomain.services.MailService.Template
+import com.github.mustachejava.DefaultMustacheFactory
+import com.twitter.mustache.ScalaObjectHandler
+
+import scala.io.Source
+
+object MailService {
+
+ trait Template {
+ val subject: String
+ def parameters: Map[String, Any]
+ def filename: String
+ val contentType: String = "text/html"
+
+ protected val factory = new DefaultMustacheFactory()
+ factory.setObjectHandler(new ScalaObjectHandler)
+
+ protected def inputStream: InputStream = getClass.getClassLoader.getResourceAsStream(filename)
+ protected def templateContent: String = Source.fromInputStream(inputStream).getLines().mkString
+
+ def content: String = {
+ val template = factory.compile(new StringReader(templateContent), filename)
+ val writer = new StringWriter
+ template
+ .execute(writer, parameters)
+ .close()
+ writer.toString
+ }
+ }
+}
+
+trait MailService {
+
+ def sendTo(email: String, template: Template): Boolean
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala
index 8ce06f2..df57e17 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala
@@ -6,7 +6,6 @@ import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
import xyz.driver.pdsuicommon.domain.LongId
import xyz.driver.pdsuicommon.error.DomainError
-import xyz.driver.pdsuicommon.logging._
import xyz.driver.pdsuidomain.entities.Message
import scala.concurrent.Future
@@ -66,10 +65,9 @@ object MessageService {
case object NotFoundError extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError
case class CommonError(userMessage: String) extends DeleteReply with DomainError
}
-
}
-trait MessageService extends PhiLogging {
+trait MessageService {
import MessageService._
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala
index 593e2ce..e23dfc5 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala
@@ -84,7 +84,10 @@ object PatientCriterionService {
object UpdateReply {
type Error = UpdateReply with DomainError
- case object Updated extends UpdateReply
+ case class Updated(x: PatientCriterion, labelId: LongId[Label], armList: List[Arm], criterionIsCompound: Boolean)
+ extends UpdateReply
+
+ case object UpdatedList extends UpdateReply
case object AuthorizationError
extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
@@ -101,7 +104,7 @@ trait PatientCriterionService {
import PatientCriterionService._
def getAll(patientId: UuidId[Patient],
- filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ origFilter: SearchFilterExpr = SearchFilterExpr.Empty,
sorting: Option[Sorting] = None,
pagination: Option[Pagination] = None)(
implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala
index 39ee2b1..a15e11f 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala
@@ -25,7 +25,7 @@ object PatientHypothesisService {
sealed trait GetListReply
object GetListReply {
- case class EntityList(xs: Seq[PatientHypothesis], totalFound: Int) extends GetListReply
+ case class EntityList(xs: Seq[(PatientHypothesis, Boolean)], totalFound: Int) extends GetListReply
case object AuthorizationError
extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
@@ -38,7 +38,7 @@ object PatientHypothesisService {
sealed trait GetByIdReply
object GetByIdReply {
- case class Entity(x: PatientHypothesis) extends GetByIdReply
+ case class Entity(x: PatientHypothesis, isRequired: Boolean) extends GetByIdReply
type Error = GetByIdReply with DomainError
@@ -53,8 +53,8 @@ object PatientHypothesisService {
case class CommonError(userMessage: String) extends GetByIdReply with DomainError
implicit def toPhiString(reply: GetByIdReply): PhiString = reply match {
- case x: DomainError => phi"GetByIdReply.Error($x)"
- case Entity(x) => phi"GetByIdReply.Entity($x)"
+ case x: DomainError => phi"GetByIdReply.Error($x)"
+ case Entity(x, isRequired) => phi"GetByIdReply.Entity($x, $isRequired)"
}
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala
index f6039f0..2586798 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala
@@ -1,7 +1,5 @@
package xyz.driver.pdsuidomain.services
-import java.time.LocalDate
-
import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
import xyz.driver.pdsuicommon.db._
import xyz.driver.pdsuicommon.domain.{LongId, UuidId}
@@ -12,21 +10,23 @@ import scala.concurrent.Future
object PatientLabelEvidenceService {
- case class Aggregated(evidence: PatientLabelEvidence, date: LocalDate, documentType: String, providerType: String)
-
trait DefaultAccessDeniedError {
def userMessage: String = "Access denied"
}
+ trait DefaultPatientNotFoundError {
+ def userMessage: String = "Patient not found"
+ }
+
sealed trait GetByIdReply
object GetByIdReply {
- case class Entity(x: Aggregated) extends GetByIdReply
+ case class Entity(x: PatientLabelEvidenceView) extends GetByIdReply
type Error = GetByIdReply with DomainError
- case class NotFoundError(userMessage: String) extends GetByIdReply with DomainError.NotFoundError
+ final case class NotFoundError(userMessage: String) extends GetByIdReply with DomainError.NotFoundError
- case class CommonError(userMessage: String) extends GetByIdReply with DomainError
+ final case class CommonError(userMessage: String) extends GetByIdReply with DomainError
case object AuthorizationError
extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
@@ -34,10 +34,13 @@ object PatientLabelEvidenceService {
sealed trait GetListReply
object GetListReply {
- case class EntityList(xs: Seq[Aggregated], totalFound: Int) extends GetListReply
+ case class EntityList(xs: Seq[PatientLabelEvidenceView], totalFound: Int) extends GetListReply
type Error = GetListReply with DomainError
+ case object PatientNotFoundError
+ extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError
+
case object AuthorizationError
extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala
new file mode 100644
index 0000000..55a408f
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala
@@ -0,0 +1,78 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.concurrent.BridgeUploadQueue
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.error.DomainError
+
+import scala.concurrent.Future
+
+object QueueUploadService {
+ trait DefaultNotFoundError {
+ def userMessage: String = "Message not found"
+ }
+
+ trait DefaultAccessDeniedError {
+ def userMessage: String = "Access denied"
+ }
+
+ sealed trait CreateReply
+ object CreateReply {
+ type Error = CreateReply with DomainError
+
+ case class Created(x: BridgeUploadQueue.Item) extends CreateReply
+ case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case class CommonError(userMessage: String) extends CreateReply with DomainError
+ }
+
+ sealed trait GetByIdReply
+ object GetByIdReply {
+ type Error = GetByIdReply with DomainError
+
+ case class Entity(x: BridgeUploadQueue.Item) extends GetByIdReply
+ case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError
+ case class CommonError(userMessage: String) extends GetByIdReply with DomainError
+ }
+
+ sealed trait GetListReply
+ object GetListReply {
+ type Error = GetListReply with DomainError
+
+ case class EntityList(xs: Seq[BridgeUploadQueue.Item],
+ totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ }
+
+ sealed trait ResetReply
+ object ResetReply {
+ type Error = ResetReply with DomainError
+
+ case class Updated(updated: BridgeUploadQueue.Item) extends ResetReply
+ case object AuthorizationError extends ResetReply with DomainError.AuthorizationError with DefaultAccessDeniedError
+ case object NotFoundError extends ResetReply with DefaultNotFoundError with DomainError.NotFoundError
+ case class CommonError(userMessage: String) extends ResetReply with DomainError
+ }
+}
+
+trait QueueUploadService {
+
+ import QueueUploadService._
+
+ def create(kind: String, tag: String)
+ (implicit requestContext: AuthenticatedRequestContext): Future[CreateReply]
+
+ def getById(kind: String, tag: String)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply]
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+
+ def reset(kind: String, tag: String)
+ (implicit requestContext: AuthenticatedRequestContext): Future[ResetReply]
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala
new file mode 100644
index 0000000..3034027
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala
@@ -0,0 +1,31 @@
+package xyz.driver.pdsuidomain.services
+
+import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.error.DomainError
+import xyz.driver.pdsuidomain.entities.UserHistory
+
+import scala.concurrent.Future
+
+object UserHistoryService {
+
+ sealed trait GetListReply
+ object GetListReply {
+ case class EntityList(xs: Seq[UserHistory], totalFound: Int) extends GetListReply
+
+ case object AuthorizationError
+ extends GetListReply with DomainError.AuthorizationError {
+ def userMessage: String = "Access denied"
+ }
+ }
+}
+
+trait UserHistoryService {
+
+ import UserHistoryService._
+
+ def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Option[Sorting] = None,
+ pagination: Option[Pagination] = None)
+ (implicit requestContext: AuthenticatedRequestContext): Future[GetListReply]
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala
index 79e9835..a1d4800 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala
@@ -4,7 +4,6 @@ import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, AuthenticatedReques
import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
import xyz.driver.pdsuicommon.domain.{Email, LongId, User}
import xyz.driver.pdsuicommon.error.DomainError
-import xyz.driver.pdsuicommon.logging._
import scala.concurrent.Future
@@ -95,10 +94,9 @@ object UserService {
case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError
case class CommonError(userMessage: String) extends DeleteReply with DomainError
}
-
}
-trait UserService extends PhiLogging {
+trait UserService {
import UserService._
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala
new file mode 100644
index 0000000..932da67
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala
@@ -0,0 +1,13 @@
+package xyz.driver.pdsuidomain.services.fake
+
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.services.MailService
+import xyz.driver.pdsuidomain.services.MailService.Template
+
+object StubMailService extends MailService with PhiLogging {
+
+ override def sendTo(email: String, template: Template): Boolean = {
+ logger.debug(phi"sendTo(email=${Unsafe(email)}")
+ true
+ }
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala
new file mode 100644
index 0000000..37dc758
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala
@@ -0,0 +1,45 @@
+package xyz.driver.pdsuidomain.services.rest
+
+import com.sendgrid._
+import xyz.driver.pdsuicommon.logging._
+import xyz.driver.pdsuidomain.services.MailService
+import xyz.driver.pdsuidomain.services.MailService.Template
+import xyz.driver.pdsuidomain.services.rest.SendGridMailService._
+
+import scala.util.control.NonFatal
+
+object SendGridMailService {
+
+ private val ExpectedHttpCode = 202
+
+ case class Settings(provider: String, frontEndUrl: String, apiKey: String, from: String)
+}
+
+class SendGridMailService(settings: Settings) extends MailService with PhiLogging {
+
+ def sendTo(email: String, template: Template): Boolean = {
+ val to = new Email(email)
+ val content = new Content(template.contentType, template.content)
+ val mail = new Mail(new Email(settings.from), template.subject, to, content)
+
+ val request = new Request()
+ val sendGrid = new SendGrid(settings.apiKey)
+
+ try {
+ request.method = Method.POST
+ request.endpoint = "mail/send"
+ request.body = mail.build()
+ val response = sendGrid.api(request)
+ if (response.statusCode != ExpectedHttpCode) {
+ logger.error(phi"Unexpected response: ${Unsafe(response.statusCode)}, ${Unsafe(response.body.take(100))}")
+ }
+
+ response.statusCode == ExpectedHttpCode
+ }
+ catch {
+ case NonFatal(e) =>
+ logger.error(phi"Can not send an email: $e")
+ false
+ }
+ }
+}
diff --git a/src/test/scala/xyz/driver/pdsuicommon/utils/StringOpsSuite.scala b/src/test/scala/xyz/driver/pdsuicommon/utils/StringOpsSuite.scala
new file mode 100644
index 0000000..9bd4382
--- /dev/null
+++ b/src/test/scala/xyz/driver/pdsuicommon/utils/StringOpsSuite.scala
@@ -0,0 +1,33 @@
+package xyz.driver.pdsuicommon.utils
+
+import xyz.driver.pdsuicommon.utils.Implicits.toStringOps
+import org.scalatest.FreeSpecLike
+
+class StringOpsSuite extends FreeSpecLike {
+
+ "safeTrim" - {
+ "empty string" in {
+ assert("".safeTrim == "")
+ }
+
+ "string with whitespace symbols" in {
+ assert("\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000".safeTrim == "")
+ }
+
+ "string with control symbols" in {
+ assert("\u001f\u007f\t\n".safeTrim == "")
+ }
+
+ "whitespaces and control symbols from the left side" in {
+ assert("\u001f\u2002\u007f\nfoo".safeTrim == "foo")
+ }
+
+ "whitespaces and control symbols from the right side" in {
+ assert("foo\u001f\u2002\u007f\n".safeTrim == "foo")
+ }
+
+ "already trimmed string" in {
+ assert("foo".safeTrim == "foo")
+ }
+ }
+} \ No newline at end of file
diff --git a/src/test/scala/xyz/driver/pdsuidomain/DocumentSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/DocumentSuite.scala
index 3955667..f851680 100644
--- a/src/test/scala/xyz/driver/pdsuidomain/DocumentSuite.scala
+++ b/src/test/scala/xyz/driver/pdsuidomain/DocumentSuite.scala
@@ -33,6 +33,35 @@ class DocumentSuite extends BaseSuite {
}
}
+ "getRequiredType" - {
+ "getOPNType" in {
+ val documentForOPNType = sampleDocument.copy(
+ typeId = Some(LongId(1L)),
+ providerTypeId = Some(LongId(1L)),
+ startDate = Some(LocalDate.now().minus(2, ChronoUnit.DAYS))
+ )
+ val r = documentForOPNType.getRequiredType("Outpatient Physician Note", "Medical Oncology")
+ assert(r.contains(Document.RequiredType.OPN), s"document should have the requiredType=OPN, but:$r")
+ }
+
+ "getPNType" in {
+ val documentForPNType = sampleDocument.copy(
+ typeId = Some(LongId(6))
+ )
+ val r = documentForPNType.getRequiredType("Pathology Report", "")
+ assert(r.contains(Document.RequiredType.PN), s"document should have the requiredType=PN, but:$r")
+ }
+ "get None" in {
+ val document = sampleDocument.copy(
+ typeId = Some(LongId(1L)),
+ providerTypeId = Some(LongId(1L)),
+ startDate = Some(LocalDate.now().minus(7, ChronoUnit.MONTHS))
+ )
+ val r = document.getRequiredType("Outpatient Physician Note", "Medical Oncology")
+ assert(r.isEmpty, s"document should have the requiredType=None, but:$r")
+ }
+ }
+
private def sampleDocument = {
val lastUpdate = LocalDateTime.now()
@@ -42,11 +71,13 @@ class DocumentSuite extends BaseSuite {
previousStatus = None,
assignee = None,
previousAssignee = None,
+ lastActiveUserId = None,
recordId = LongId(2003),
physician = None,
typeId = Some(LongId(123)),
providerName = Some("etst"),
providerTypeId = Some(LongId(123)),
+ requiredType = None,
startDate = Some(lastUpdate.toLocalDate.minusDays(2)),
endDate = None,
lastUpdate = lastUpdate,